1- import { forwardRef } from 'react' ;
1+ import { forwardRef , useCallback , useContext , useEffect , useRef , useState } from 'react' ;
22import { css } from '@patternfly/react-styles' ;
33import styles from '@patternfly/react-styles/css/components/Table/table' ;
4+ import { TableContext } from './Table' ;
5+
6+ /** Ratio must be below this to count as “pinned” (avoids doc-layout subpixel + strict threshold: [1] never hitting exactly 1). */
7+ const PINNED_INTERSECTION_RATIO = 0.999 ;
8+
9+ const getOverflowScrollParent = ( node : HTMLElement ) : Element | null => {
10+ let parent = node . parentElement ;
11+ while ( parent ) {
12+ const style = getComputedStyle ( parent ) ;
13+ if ( / ( a u t o | s c r o l l | o v e r l a y ) / . test ( style . overflowY ) || / ( a u t o | s c r o l l | o v e r l a y ) / . test ( style . overflowX ) ) {
14+ return parent ;
15+ }
16+ parent = parent . parentElement ;
17+ }
18+ return null ;
19+ } ;
20+
21+ const assignRef = < T , > ( ref : React . Ref < T > | undefined , value : T | null ) => {
22+ if ( ! ref ) {
23+ return ;
24+ }
25+ if ( typeof ref === 'function' ) {
26+ ref ( value ) ;
27+ } else {
28+ ( ref as React . MutableRefObject < T | null > ) . current = value ;
29+ }
30+ } ;
431
532export interface TheadProps extends React . HTMLProps < HTMLTableSectionElement > {
633 /** Content rendered inside the <thead> row group */
@@ -22,20 +49,62 @@ const TheadBase: React.FunctionComponent<TheadProps> = ({
2249 innerRef,
2350 hasNestedHeader,
2451 ...props
25- } : TheadProps ) => (
26- < thead
27- className = { css (
28- styles . tableThead ,
29- className ,
30- noWrap && styles . modifiers . nowrap ,
31- hasNestedHeader && styles . modifiers . nestedColumnHeader
32- ) }
33- ref = { innerRef }
34- { ...props }
35- >
36- { children }
37- </ thead >
38- ) ;
52+ } : TheadProps ) => {
53+ const { isStickyHeader } = useContext ( TableContext ) ;
54+ const observeStickyPin = ! ! isStickyHeader ;
55+ const [ isPinned , setIsPinned ] = useState ( false ) ;
56+ const theadElRef = useRef < HTMLTableSectionElement | null > ( null ) ;
57+
58+ const setTheadRef = useCallback (
59+ ( node : HTMLTableSectionElement | null ) => {
60+ theadElRef . current = node ;
61+ assignRef ( innerRef , node ) ;
62+ } ,
63+ [ innerRef ]
64+ ) ;
65+
66+ useEffect ( ( ) => {
67+ if ( ! observeStickyPin || typeof IntersectionObserver === 'undefined' ) {
68+ setIsPinned ( false ) ;
69+ return ;
70+ }
71+
72+ const el = theadElRef . current ;
73+ if ( ! el ) {
74+ return ;
75+ }
76+
77+ const scrollRoot = getOverflowScrollParent ( el ) ;
78+
79+ // Requires sticky thead `inset-block-start: -1px` in CSS (see table.css).
80+ const observer = new IntersectionObserver (
81+ ( [ entry ] ) => {
82+ // console.log(scrollRoot, entry, entry.intersectionRatio);
83+ setIsPinned ( entry . intersectionRatio < PINNED_INTERSECTION_RATIO ) ;
84+ } ,
85+ { threshold : [ 0 , 1 ] , root : scrollRoot }
86+ ) ;
87+
88+ observer . observe ( el ) ;
89+ return ( ) => observer . disconnect ( ) ;
90+ } , [ observeStickyPin ] ) ;
91+
92+ return (
93+ < thead
94+ className = { css (
95+ styles . tableThead ,
96+ className ,
97+ noWrap && styles . modifiers . nowrap ,
98+ hasNestedHeader && styles . modifiers . nestedColumnHeader ,
99+ observeStickyPin && isPinned && 'PINNED'
100+ ) }
101+ ref = { setTheadRef }
102+ { ...props }
103+ >
104+ { children }
105+ </ thead >
106+ ) ;
107+ } ;
39108
40109export const Thead = forwardRef ( ( props : TheadProps , ref : React . Ref < HTMLTableSectionElement > ) => (
41110 < TheadBase { ...props } innerRef = { ref } />
0 commit comments