1 import React
from 'react';
2 import PropTypes
from 'prop-types';
3 import scheduleIdleTask
from '../features/ui/util/schedule_idle_task';
4 import getRectFromEntry
from '../features/ui/util/get_rect_from_entry';
5 import { is
} from 'immutable';
7 // Diff these props in the "rendered" state
8 const updateOnPropsForRendered
= ['id', 'index', 'listLength'];
9 // Diff these props in the "unrendered" state
10 const updateOnPropsForUnrendered
= ['id', 'index', 'listLength', 'cachedHeight'];
12 export default class IntersectionObserverArticle
extends React
.Component
{
15 intersectionObserverWrapper: PropTypes
.object
.isRequired
,
16 id: PropTypes
.oneOfType([PropTypes
.string
, PropTypes
.number
]),
17 index: PropTypes
.oneOfType([PropTypes
.string
, PropTypes
.number
]),
18 listLength: PropTypes
.oneOfType([PropTypes
.string
, PropTypes
.number
]),
19 saveHeightKey: PropTypes
.string
,
20 cachedHeight: PropTypes
.number
,
21 onHeightChange: PropTypes
.func
,
22 children: PropTypes
.node
,
23 currentlyViewing: PropTypes
.number
,
24 updateCurrentlyViewing: PropTypes
.func
,
28 isHidden: false, // set to true in requestIdleCallback to trigger un-render
31 shouldComponentUpdate (nextProps
, nextState
) {
32 const isUnrendered
= !this.state
.isIntersecting
&& (this.state
.isHidden
|| this.props
.cachedHeight
);
33 const willBeUnrendered
= !nextState
.isIntersecting
&& (nextState
.isHidden
|| nextProps
.cachedHeight
);
34 if (!!isUnrendered
!== !!willBeUnrendered
) {
35 // If we're going from rendered to unrendered (or vice versa) then update
38 // Otherwise, diff based on props
39 const propsToDiff
= isUnrendered
? updateOnPropsForUnrendered : updateOnPropsForRendered
;
40 return !propsToDiff
.every(prop
=> is(nextProps
[prop
], this.props
[prop
]));
43 componentDidMount () {
44 const { intersectionObserverWrapper
, id
} = this.props
;
46 intersectionObserverWrapper
.observe(
49 this.handleIntersection
52 this.componentMounted
= true;
54 if(id
=== this.props
.currentlyViewing
) this.node
.scrollIntoView();
57 componentWillUnmount () {
58 const { intersectionObserverWrapper
, id
} = this.props
;
59 intersectionObserverWrapper
.unobserve(id
, this.node
);
61 this.componentMounted
= false;
64 handleIntersection
= (entry
) => {
67 if(entry
.intersectionRatio
> 0.75 && this.props
.updateCurrentlyViewing
) this.props
.updateCurrentlyViewing(this.id
);
69 scheduleIdleTask(this.calculateHeight
);
70 this.setState(this.updateStateAfterIntersection
);
73 updateStateAfterIntersection
= (prevState
) => {
74 if (prevState
.isIntersecting
!== false && !this.entry
.isIntersecting
) {
75 scheduleIdleTask(this.hideIfNotIntersecting
);
78 isIntersecting: this.entry
.isIntersecting
,
83 calculateHeight
= () => {
84 const { onHeightChange
, saveHeightKey
, id
} = this.props
;
85 // save the height of the fully-rendered element (this is expensive
86 // on Chrome, where we need to fall back to getBoundingClientRect)
87 this.height
= getRectFromEntry(this.entry
).height
;
89 if (onHeightChange
&& saveHeightKey
) {
90 onHeightChange(saveHeightKey
, id
, this.height
);
94 hideIfNotIntersecting
= () => {
95 if (!this.componentMounted
) {
99 // When the browser gets a chance, test if we're still not intersecting,
100 // and if so, set our isHidden to true to trigger an unrender. The point of
101 // this is to save DOM nodes and avoid using up too much memory.
102 // See: https://github.com/tootsuite/mastodon/issues/2900
103 this.setState((prevState
) => ({ isHidden: !prevState
.isIntersecting
}));
106 handleRef
= (node
) => {
111 const { children
, id
, index
, listLength
, cachedHeight
} = this.props
;
112 const { isIntersecting
, isHidden
} = this.state
;
114 if (!isIntersecting
&& (isHidden
|| cachedHeight
)) {
118 aria
-posinset
={index
+ 1}
119 aria
-setsize
={listLength
}
120 style
={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
124 {children
&& React
.cloneElement(children
, { hidden: true })}
130 <article ref
={this.handleRef
} aria
-posinset
={index
+ 1} aria
-setsize
={listLength
} data
-id
={id
} tabIndex
='0'>
131 {children
&& React
.cloneElement(children
, { hidden: false })}