@@ -22,7 +22,8 @@ function calculateMaxLines(height: number) {
2222const BlockCodeViewer : React . FC < IBlockCodeViewerProps > = ( { className, language, value, showLineNumbers, ...rest } ) => {
2323 const nodeRef = React . useRef < HTMLPreElement | null > ( null ) ;
2424 const [ maxLines , setMaxLines ] = React . useState < number | null > ( null ) ;
25- const observerRef = React . useRef ( new ObservableSet ( ) ) ;
25+ const [ observer , setObserver ] = React . useState < IntersectionObserver > ( ) ;
26+ const viewportSet = React . useRef ( new ObservableSet ( ) ) ;
2627 const slicedBlocks = useSlicedBlocks ( value , maxLines === null ? null : Math . max ( 0 , maxLines - 1 ) ) ;
2728 const lineNumberCharacterCount = String (
2829 slicedBlocks !== null && maxLines !== null ? slicedBlocks . length * maxLines : 0 ,
@@ -31,67 +32,60 @@ const BlockCodeViewer: React.FC<IBlockCodeViewerProps> = ({ className, language,
3132 React . useLayoutEffect ( ( ) => {
3233 if ( nodeRef . current !== null ) {
3334 setMaxLines ( calculateMaxLines ( window . innerHeight ) ) ; // we have to use window here, as element may not ave any height at this time
34- highlightRelevantParts ( nodeRef . current ) ;
3535 }
36- // eslint-disable-next-line react-hooks/exhaustive-deps
3736 } , [ nodeRef ] ) ;
3837
39- useResizeObserver ( {
40- onResize : debounce ( ( { height } ) => {
41- const newMaxLines = calculateMaxLines ( height ) ;
42- if ( newMaxLines !== maxLines ) {
43- setMaxLines ( newMaxLines ) ;
44- }
45- } , 250 ) ,
46- ref : nodeRef ,
47- } ) ;
48-
49- function highlightRelevantParts ( target : EventTarget ) {
50- if ( slicedBlocks === null || maxLines === null ) return ;
51-
52- const value =
53- ( target === nodeRef . current ? nodeRef . current . scrollTop : window . pageYOffset ) /
54- ( SINGLE_LINE_SIZE * maxLines - SINGLE_LINE_SIZE ) ;
55- const blockNo = Math . round ( value ) ;
56-
57- // see https://github.com/stoplightio/ui-kit/pull/180 for the reasoning
58- observerRef . current . add ( blockNo ) ;
59- observerRef . current . add ( Math . min ( slicedBlocks . length - 1 , blockNo + 1 ) ) ;
60- observerRef . current . add ( Math . max ( 0 , blockNo - 1 ) ) ;
61-
62- if ( value > blockNo ) {
63- observerRef . current . add ( Math . min ( slicedBlocks . length - 1 , blockNo + 2 ) ) ;
64- } else {
65- observerRef . current . add ( Math . max ( 0 , blockNo - 2 ) ) ;
66- }
67- }
68-
6938 React . useEffect ( ( ) => {
70- observerRef . current . clear ( ) ;
71- } , [ maxLines ] ) ;
39+ const { current : viewport } = viewportSet ;
40+ if ( nodeRef . current === null || maxLines === null ) {
41+ return ;
42+ }
7243
73- React . useEffect ( ( ) => {
74- const { current : root } = nodeRef ;
44+ const observer = new IntersectionObserver (
45+ entries => {
46+ for ( const entry of entries ) {
47+ if ( ! entry . isIntersecting ) continue ;
7548
76- if ( root === null || maxLines === null ) return ;
49+ viewport . add ( entry . target ) ;
50+ const { previousElementSibling, nextElementSibling } = entry . target ;
7751
78- const handler : EventListener = debounce ( e => {
79- if ( e . target !== null ) {
80- highlightRelevantParts ( e . target ) ;
81- }
82- } , 32 ) ;
52+ // highlight siblings to reduce flickering while scrolling using page up/down
53+ if ( previousElementSibling ?. tagName === 'DIV' ) {
54+ viewport . add ( previousElementSibling ) ;
55+ }
8356
84- highlightRelevantParts ( root . offsetHeight > window . innerHeight ? window : root ) ;
57+ if ( nextElementSibling ?. tagName === 'DIV' ) {
58+ viewport . add ( nextElementSibling ) ;
59+ }
60+ }
61+ } ,
62+ {
63+ root : null ,
64+ threshold : 0 ,
65+ } ,
66+ ) ;
8567
86- window . addEventListener ( 'scroll' , handler , { passive : true } ) ;
87- root . addEventListener ( 'scroll' , handler , { passive : true } ) ;
68+ setObserver ( observer ) ;
8869
8970 return ( ) => {
90- root . removeEventListener ( 'scroll' , handler ) ;
91- window . removeEventListener ( 'scroll' , handler ) ;
71+ setObserver ( void 0 ) ;
72+ observer . disconnect ( ) ;
9273 } ;
93- // eslint-disable-next-line react-hooks/exhaustive-deps
94- } , [ observerRef , nodeRef , maxLines ] ) ;
74+ } , [ nodeRef , maxLines ] ) ;
75+
76+ useResizeObserver ( {
77+ onResize : debounce (
78+ ( { height } ) => {
79+ const newMaxLines = calculateMaxLines ( height ) ;
80+ if ( newMaxLines !== maxLines ) {
81+ setMaxLines ( newMaxLines ) ;
82+ }
83+ } ,
84+ 250 ,
85+ { leading : true } ,
86+ ) ,
87+ ref : nodeRef ,
88+ } ) ;
9589
9690 return (
9791 < pre
@@ -101,15 +95,15 @@ const BlockCodeViewer: React.FC<IBlockCodeViewerProps> = ({ className, language,
10195 } ) }
10296 { ...rest }
10397 >
104- { slicedBlocks ?. map ( ( value , index ) => (
98+ { slicedBlocks ?. map ( ( { id , value } , index , blocks ) => (
10599 < SingleCodeBlock
106- key = { index }
100+ key = { id }
107101 value = { value }
108102 language = { language }
109103 showLineNumbers = { showLineNumbers }
110- index = { index }
111- lineNumber = { maxLines === null ? 0 : index * maxLines + 1 }
112- observer = { observerRef . current }
104+ lineNumber = { ( index > 0 ? blocks [ index - 1 ] . lineCount : 0 ) + 1 }
105+ observer = { observer }
106+ viewport = { viewportSet . current }
113107 />
114108 ) ) }
115109 </ pre >
0 commit comments