1 import React
from 'react';
2 import PropTypes
from 'prop-types';
3 import ImmutablePureComponent
from 'react-immutable-pure-component';
4 import scheduleIdleTask
from '../features/ui/util/schedule_idle_task';
5 import getRectFromEntry
from '../features/ui/util/get_rect_from_entry';
7 export default class IntersectionObserverArticle
extends ImmutablePureComponent
{
10 intersectionObserverWrapper: PropTypes
.object
.isRequired
,
11 id: PropTypes
.oneOfType([PropTypes
.string
, PropTypes
.number
]),
12 index: PropTypes
.oneOfType([PropTypes
.string
, PropTypes
.number
]),
13 listLength: PropTypes
.oneOfType([PropTypes
.string
, PropTypes
.number
]),
14 saveHeightKey: PropTypes
.string
,
15 cachedHeight: PropTypes
.number
,
16 onHeightChange: PropTypes
.func
,
17 children: PropTypes
.node
,
21 isHidden: false, // set to true in requestIdleCallback to trigger un-render
24 shouldComponentUpdate (nextProps
, nextState
) {
25 if (!nextState
.isIntersecting
&& nextState
.isHidden
) {
26 // It's only if we're not intersecting (i.e. offscreen) and isHidden is true
27 // that either "isIntersecting" or "isHidden" matter, and then they're
28 // the only things that matter (and updated ARIA attributes).
29 return this.state
.isIntersecting
|| !this.state
.isHidden
|| nextProps
.listLength
!== this.props
.listLength
;
30 } else if (nextState
.isIntersecting
&& !this.state
.isIntersecting
) {
31 // If we're going from a non-intersecting state to an intersecting state,
32 // (i.e. offscreen to onscreen), then we definitely need to re-render
35 // Otherwise, diff based on "updateOnProps" and "updateOnStates"
36 return super.shouldComponentUpdate(nextProps
, nextState
);
39 componentDidMount () {
40 const { intersectionObserverWrapper
, id
} = this.props
;
42 intersectionObserverWrapper
.observe(
45 this.handleIntersection
48 this.componentMounted
= true;
51 componentWillUnmount () {
52 const { intersectionObserverWrapper
, id
} = this.props
;
53 intersectionObserverWrapper
.unobserve(id
, this.node
);
55 this.componentMounted
= false;
58 handleIntersection
= (entry
) => {
59 const { onHeightChange
, saveHeightKey
, id
} = this.props
;
61 if (this.node
&& this.node
.children
.length
!== 0) {
62 // save the height of the fully-rendered element
63 this.height
= getRectFromEntry(entry
).height
;
65 if (onHeightChange
&& saveHeightKey
) {
66 onHeightChange(saveHeightKey
, id
, this.height
);
70 this.setState((prevState
) => {
71 if (prevState
.isIntersecting
&& !entry
.isIntersecting
) {
72 scheduleIdleTask(this.hideIfNotIntersecting
);
75 isIntersecting: entry
.isIntersecting
,
81 hideIfNotIntersecting
= () => {
82 if (!this.componentMounted
) {
86 // When the browser gets a chance, test if we're still not intersecting,
87 // and if so, set our isHidden to true to trigger an unrender. The point of
88 // this is to save DOM nodes and avoid using up too much memory.
89 // See: https://github.com/tootsuite/mastodon/issues/2900
90 this.setState((prevState
) => ({ isHidden: !prevState
.isIntersecting
}));
93 handleRef
= (node
) => {
98 const { children
, id
, index
, listLength
, cachedHeight
} = this.props
;
99 const { isIntersecting
, isHidden
} = this.state
;
101 if (!isIntersecting
&& (isHidden
|| cachedHeight
)) {
105 aria
-posinset
={index
}
106 aria
-setsize
={listLength
}
107 style
={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
111 {children
&& React
.cloneElement(children
, { hidden: true })}
117 <article ref
={this.handleRef
} aria
-posinset
={index
} aria
-setsize
={listLength
} data
-id
={id
} tabIndex
='0'>
118 {children
&& React
.cloneElement(children
, { hidden: false })}