@@ -308,9 +308,266 @@ function loadProps() {
308308 console . log ( 'parsed props' , props ) ;
309309}
310310
311+ /**
312+ * Helper function to apply sticky styling to a single column
313+ * Used for reports where only the first column should be frozen:
314+ * - Custom Labels (rowspan=2, colspan=1) - 180px width
315+ * - Apex, LWC, Experience Sites, FlexiPage (single-level header) - 250px width
316+ */
317+ function applySingleColumnFreeze ( table , headerCell , columnWidth = 180 ) {
318+ // Default to 180px for Custom Labels, but can be overridden for other reports
319+
320+ // Apply styles to first header cell
321+ applyStickyStyles ( headerCell , columnWidth , 0 , 20 , '#f3f3f3' , true ) ;
322+
323+ // Ensure all other header cells in both rows are NOT sticky
324+ const thead = table . querySelector ( 'thead' ) ;
325+ if ( thead ) {
326+ const allHeaderCells = thead . querySelectorAll ( 'th' ) ;
327+ allHeaderCells . forEach ( ( th , index ) => {
328+ if ( index > 0 ) {
329+ removeStickyStyles ( th ) ;
330+ }
331+ } ) ;
332+ }
333+
334+ // Apply styles to body cells in first column
335+ const tbody = table . querySelector ( 'tbody' ) ;
336+ if ( tbody ) {
337+ const rows = tbody . querySelectorAll ( 'tr' ) ;
338+ rows . forEach ( ( row ) => {
339+ const firstCell = row . querySelector ( 'td:first-child' ) ;
340+ if ( firstCell ) {
341+ applyStickyStyles ( firstCell , columnWidth , 0 , 10 , '#fff' , true ) ;
342+ }
343+ } ) ;
344+
345+ // Ensure all other columns are NOT sticky
346+ rows . forEach ( ( row ) => {
347+ const cells = Array . from ( row . querySelectorAll ( 'td' ) ) ;
348+ cells . forEach ( ( cell , index ) => {
349+ if ( index > 0 ) {
350+ removeStickyStyles ( cell ) ;
351+ }
352+ } ) ;
353+ } ) ;
354+
355+ // Add hover effect for first column
356+ rows . forEach ( ( row ) => {
357+ row . addEventListener ( 'mouseenter' , ( ) => {
358+ const firstCell = row . querySelector ( 'td:first-child' ) ;
359+ if ( firstCell ) {
360+ firstCell . style . setProperty ( 'background-color' , '#f3f3f3' , 'important' ) ;
361+ }
362+ } ) ;
363+
364+ row . addEventListener ( 'mouseleave' , ( ) => {
365+ const firstCell = row . querySelector ( 'td:first-child' ) ;
366+ if ( firstCell ) {
367+ firstCell . style . setProperty ( 'background-color' , '#fff' , 'important' ) ;
368+ }
369+ } ) ;
370+ } ) ;
371+ }
372+ }
373+
374+ /**
375+ * Helper function to apply sticky styles to a cell
376+ */
377+ function applyStickyStyles ( element , width , left , zIndex , bgColor , addBorder = false ) {
378+ element . style . setProperty ( 'width' , `${ width } px` , 'important' ) ;
379+ element . style . setProperty ( 'min-width' , `${ width } px` , 'important' ) ;
380+ element . style . setProperty ( 'max-width' , `${ width } px` , 'important' ) ;
381+ element . style . setProperty ( 'position' , 'sticky' , 'important' ) ;
382+ element . style . setProperty ( 'left' , `${ left } px` , 'important' ) ;
383+ element . style . setProperty ( 'z-index' , `${ zIndex } ` , 'important' ) ;
384+ element . style . setProperty ( 'background-color' , bgColor , 'important' ) ;
385+ if ( addBorder ) {
386+ element . style . setProperty ( 'border-right' , '1px solid #e5e5e5' , 'important' ) ;
387+ }
388+ }
389+
390+ /**
391+ * Helper function to remove sticky styles from a cell
392+ */
393+ function removeStickyStyles ( element ) {
394+ element . style . setProperty ( 'position' , 'static' , 'important' ) ;
395+ element . style . setProperty ( 'left' , 'auto' , 'important' ) ;
396+ element . style . setProperty ( 'width' , 'auto' , 'important' ) ;
397+ element . style . setProperty ( 'min-width' , 'auto' , 'important' ) ;
398+ element . style . setProperty ( 'max-width' , 'none' , 'important' ) ;
399+ }
400+
401+ /**
402+ * Apply sticky styles to header cells for multi-column freeze
403+ */
404+ function applyMultiColumnHeaderFreeze ( firstHeaderCell , secondRow , colspan , baseColumnWidth , firstColumnWidth ) {
405+ // Calculate total width for the first header
406+ const totalHeaderWidth = firstColumnWidth + ( colspan - 1 ) * baseColumnWidth ;
407+
408+ // Apply styles to first row first header
409+ applyStickyStyles ( firstHeaderCell , totalHeaderWidth , 0 , 20 , '#f3f3f3' , true ) ;
410+
411+ // Apply styles to second row headers (sub-headers under the first header)
412+ const secondRowHeaders = Array . from ( secondRow . querySelectorAll ( 'th' ) ) ;
413+ let cumulativeLeft = 0 ;
414+
415+ for ( let i = 0 ; i < secondRowHeaders . length ; i ++ ) {
416+ const header = secondRowHeaders [ i ] ;
417+
418+ if ( i < colspan ) {
419+ // Freeze this column
420+ const columnWidth = i === 0 ? firstColumnWidth : baseColumnWidth ;
421+ const isLastFrozen = i === colspan - 1 ;
422+ applyStickyStyles ( header , columnWidth , cumulativeLeft , 20 , '#f3f3f3' , isLastFrozen ) ;
423+ cumulativeLeft += columnWidth ;
424+ } else {
425+ // Ensure non-frozen columns are NOT sticky
426+ removeStickyStyles ( header ) ;
427+ }
428+ }
429+ }
430+
431+ /**
432+ * Remove sticky styles from non-frozen first-row headers
433+ */
434+ function removeNonFrozenHeaderStyles ( firstRow ) {
435+ const firstRowHeaders = Array . from ( firstRow . querySelectorAll ( 'th' ) ) ;
436+ for ( let i = 1 ; i < firstRowHeaders . length ; i ++ ) {
437+ removeStickyStyles ( firstRowHeaders [ i ] ) ;
438+ }
439+ }
440+
441+ /**
442+ * Apply sticky styles to body cells for multi-column freeze
443+ */
444+ function applyMultiColumnBodyFreeze ( tbody , colspan , baseColumnWidth , firstColumnWidth ) {
445+ const rows = tbody . querySelectorAll ( 'tr' ) ;
446+
447+ rows . forEach ( ( row ) => {
448+ const cells = Array . from ( row . querySelectorAll ( 'td' ) ) ;
449+ let cumulativeLeft = 0 ;
450+
451+ for ( let i = 0 ; i < cells . length ; i ++ ) {
452+ const cell = cells [ i ] ;
453+
454+ if ( i < colspan ) {
455+ // Freeze this column
456+ const columnWidth = i === 0 ? firstColumnWidth : baseColumnWidth ;
457+ const isLastFrozen = i === colspan - 1 ;
458+ applyStickyStyles ( cell , columnWidth , cumulativeLeft , 10 , '#fff' , isLastFrozen ) ;
459+ cumulativeLeft += columnWidth ;
460+ } else {
461+ // Ensure non-frozen columns are NOT sticky
462+ removeStickyStyles ( cell ) ;
463+ }
464+ }
465+ } ) ;
466+ }
467+
468+ /**
469+ * Add hover effects to frozen cells
470+ */
471+ function addHoverEffects ( tbody , colspan ) {
472+ const rows = tbody . querySelectorAll ( 'tr' ) ;
473+
474+ rows . forEach ( ( row ) => {
475+ row . addEventListener ( 'mouseenter' , ( ) => {
476+ const cells = Array . from ( row . querySelectorAll ( 'td' ) ) ;
477+ for ( let i = 0 ; i < colspan && i < cells . length ; i ++ ) {
478+ cells [ i ] . style . setProperty ( 'background-color' , '#f3f3f3' , 'important' ) ;
479+ }
480+ } ) ;
481+
482+ row . addEventListener ( 'mouseleave' , ( ) => {
483+ const cells = Array . from ( row . querySelectorAll ( 'td' ) ) ;
484+ for ( let i = 0 ; i < colspan && i < cells . length ; i ++ ) {
485+ cells [ i ] . style . setProperty ( 'background-color' , '#fff' , 'important' ) ;
486+ }
487+ } ) ;
488+ } ) ;
489+ }
490+
491+ /**
492+ * Handle multi-column freeze (for reports with colspan > 1)
493+ */
494+ function handleMultiColumnFreeze ( table , firstHeaderCell , firstRow , secondRow , colspan ) {
495+ const baseColumnWidth = 200 ;
496+ const firstColumnWidth = 250 ;
497+
498+ // Apply header styles
499+ applyMultiColumnHeaderFreeze ( firstHeaderCell , secondRow , colspan , baseColumnWidth , firstColumnWidth ) ;
500+
501+ // Remove sticky from other first-row headers
502+ removeNonFrozenHeaderStyles ( firstRow ) ;
503+
504+ // Apply body cell styles
505+ const tbody = table . querySelector ( 'tbody' ) ;
506+ if ( tbody ) {
507+ applyMultiColumnBodyFreeze ( tbody , colspan , baseColumnWidth , firstColumnWidth ) ;
508+ addHoverEffects ( tbody , colspan ) ;
509+ }
510+ }
511+
512+ /**
513+ * Process a single table for sticky column application
514+ */
515+ function processTable ( table ) {
516+ const thead = table . querySelector ( 'thead' ) ;
517+ if ( ! thead ) return ;
518+
519+ const firstRow = thead . querySelector ( 'tr:first-child' ) ;
520+ const secondRow = thead . querySelector ( 'tr:nth-child(2)' ) ;
521+
522+ if ( ! firstRow ) return ;
523+
524+ // Get the first header cell and its colspan/rowspan
525+ const firstHeaderCell = firstRow . querySelector ( 'th:first-child' ) ;
526+ if ( ! firstHeaderCell ) return ;
527+
528+ const colspan = parseInt ( firstHeaderCell . getAttribute ( 'colspan' ) || '1' , 10 ) ;
529+ const rowspan = parseInt ( firstHeaderCell . getAttribute ( 'rowspan' ) || '1' , 10 ) ;
530+
531+ // Handle single column with rowspan=2 (e.g., Custom Labels)
532+ if ( rowspan === 2 && colspan === 1 ) {
533+ applySingleColumnFreeze ( table , firstHeaderCell , 180 ) ;
534+ return ;
535+ }
536+
537+ // Handle single-level header (e.g., Apex, LWC, Experience Sites, FlexiPage)
538+ if ( rowspan === 1 && colspan === 1 && ! secondRow ) {
539+ applySingleColumnFreeze ( table , firstHeaderCell , 250 ) ;
540+ return ;
541+ }
542+
543+ // Handle multi-column freeze with colspan
544+ if ( secondRow && colspan > 1 ) {
545+ handleMultiColumnFreeze ( table , firstHeaderCell , firstRow , secondRow , colspan ) ;
546+ }
547+ }
548+
549+ /**
550+ * Main function: Dynamically applies sticky column styling to all tables
551+ * Reads the table structure to determine how many columns should be frozen
552+ */
553+ function applyDynamicStickyColumns ( ) {
554+ const tableContainers = document . querySelectorAll ( '.table-container' ) ;
555+
556+ tableContainers . forEach ( ( container ) => {
557+ const table = container . querySelector ( 'table.slds-table' ) ;
558+ if ( table ) {
559+ processTable ( table ) ;
560+ }
561+ } ) ;
562+ }
563+
564+ // Run on page load
565+ document . addEventListener ( 'DOMContentLoaded' , applyDynamicStickyColumns ) ;
566+
311567// Expose globally so HTML inline event handlers can access them
312568window . toggleFilterDropdown = toggleFilterDropdown ;
313569window . closeFilterDropdown = closeFilterDropdown ;
314570window . closeAllFilterDropdowns = closeAllFilterDropdowns ;
315571window . filterAndSearchTable = filterAndSearchTable ;
316572window . toggleCtaSummaryPanel = toggleCtaSummaryPanel ;
573+ window . applyDynamicStickyColumns = applyDynamicStickyColumns ;
0 commit comments