import * as React from 'react'; import { useEffect } from 'react'; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } const observerMap = new Map(); const RootIds = new WeakMap(); let rootId = 0; let unsupportedValue = undefined; /** * What should be the default behavior if the IntersectionObserver is unsupported? * Ideally the polyfill has been loaded, you can have the following happen: * - `undefined`: Throw an error * - `true` or `false`: Set the `inView` value to this regardless of intersection state * **/ function defaultFallbackInView(inView) { unsupportedValue = inView; } /** * Generate a unique ID for the root element * @param root */ function getRootId(root) { if (!root) return '0'; if (RootIds.has(root)) return RootIds.get(root); rootId += 1; RootIds.set(root, rootId.toString()); return RootIds.get(root); } /** * Convert the options to a string Id, based on the values. * Ensures we can reuse the same observer when observing elements with the same options. * @param options */ function optionsToId(options) { return Object.keys(options).sort().filter(key => options[key] !== undefined).map(key => { return `${key}_${key === 'root' ? getRootId(options.root) : options[key]}`; }).toString(); } function createObserver(options) { // Create a unique ID for this observer instance, based on the root, root margin and threshold. let id = optionsToId(options); let instance = observerMap.get(id); if (!instance) { // Create a map of elements this observer is going to observe. Each element has a list of callbacks that should be triggered, once it comes into view. const elements = new Map(); let thresholds; const observer = new IntersectionObserver(entries => { entries.forEach(entry => { var _elements$get; // While it would be nice if you could just look at isIntersecting to determine if the component is inside the viewport, browsers can't agree on how to use it. // -Firefox ignores `threshold` when considering `isIntersecting`, so it will never be false again if `threshold` is > 0 const inView = entry.isIntersecting && thresholds.some(threshold => entry.intersectionRatio >= threshold); // @ts-ignore support IntersectionObserver v2 if (options.trackVisibility && typeof entry.isVisible === 'undefined') { // The browser doesn't support Intersection Observer v2, falling back to v1 behavior. // @ts-ignore entry.isVisible = inView; } (_elements$get = elements.get(entry.target)) == null ? void 0 : _elements$get.forEach(callback => { callback(inView, entry); }); }); }, options); // Ensure we have a valid thresholds array. If not, use the threshold from the options thresholds = observer.thresholds || (Array.isArray(options.threshold) ? options.threshold : [options.threshold || 0]); instance = { id, observer, elements }; observerMap.set(id, instance); } return instance; } /** * @param element - DOM Element to observe * @param callback - Callback function to trigger when intersection status changes * @param options - Intersection Observer options * @param fallbackInView - Fallback inView value. * @return Function - Cleanup function that should be triggered to unregister the observer */ function observe(element, callback, options = {}, fallbackInView = unsupportedValue) { if (typeof window.IntersectionObserver === 'undefined' && fallbackInView !== undefined) { const bounds = element.getBoundingClientRect(); callback(fallbackInView, { isIntersecting: fallbackInView, target: element, intersectionRatio: typeof options.threshold === 'number' ? options.threshold : 0, time: 0, boundingClientRect: bounds, intersectionRect: bounds, rootBounds: bounds }); return () => {// Nothing to cleanup }; } // An observer with the same options can be reused, so lets use this fact const { id, observer, elements } = createObserver(options); // Register the callback listener for this element let callbacks = elements.get(element) || []; if (!elements.has(element)) { elements.set(element, callbacks); } callbacks.push(callback); observer.observe(element); return function unobserve() { // Remove the callback from the callback list callbacks.splice(callbacks.indexOf(callback), 1); if (callbacks.length === 0) { // No more callback exists for element, so destroy it elements.delete(element); observer.unobserve(element); } if (elements.size === 0) { // No more elements are being observer by this instance, so destroy it observer.disconnect(); observerMap.delete(id); } }; } const _excluded = ["children", "as", "tag", "triggerOnce", "threshold", "root", "rootMargin", "onChange", "skip", "trackVisibility", "delay", "initialInView", "fallbackInView"]; function isPlainChildren(props) { return typeof props.children !== 'function'; } /** ## Render props To use the `` component, you pass it a function. It will be called whenever the state changes, with the new value of `inView`. In addition to the `inView` prop, children also receive a `ref` that should be set on the containing DOM element. This is the element that the IntersectionObserver will monitor. If you need it, you can also access the [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) on `entry`, giving you access to all the details about the current intersection state. ```jsx import { InView } from 'react-intersection-observer'; const Component = () => ( {({ inView, ref, entry }) => (

{`Header inside viewport ${inView}.`}

)}
); export default Component; ``` ## Plain children You can pass any element to the ``, and it will handle creating the wrapping DOM element. Add a handler to the `onChange` method, and control the state in your own component. Any extra props you add to `` will be passed to the HTML element, allowing you set the `className`, `style`, etc. ```jsx import { InView } from 'react-intersection-observer'; const Component = () => ( console.log('Inview:', inView)}>

Plain children are always rendered. Use onChange to monitor state.

); export default Component; ``` */ class InView extends React.Component { constructor(props) { super(props); this.node = null; this._unobserveCb = null; this.handleNode = node => { if (this.node) { // Clear the old observer, before we start observing a new element this.unobserve(); if (!node && !this.props.triggerOnce && !this.props.skip) { // Reset the state if we get a new node, and we aren't ignoring updates this.setState({ inView: !!this.props.initialInView, entry: undefined }); } } this.node = node ? node : null; this.observeNode(); }; this.handleChange = (inView, entry) => { if (inView && this.props.triggerOnce) { // If `triggerOnce` is true, we should stop observing the element. this.unobserve(); } if (!isPlainChildren(this.props)) { // Store the current State, so we can pass it to the children in the next render update // There's no reason to update the state for plain children, since it's not used in the rendering. this.setState({ inView, entry }); } if (this.props.onChange) { // If the user is actively listening for onChange, always trigger it this.props.onChange(inView, entry); } }; this.state = { inView: !!props.initialInView, entry: undefined }; } componentDidUpdate(prevProps) { // If a IntersectionObserver option changed, reinit the observer if (prevProps.rootMargin !== this.props.rootMargin || prevProps.root !== this.props.root || prevProps.threshold !== this.props.threshold || prevProps.skip !== this.props.skip || prevProps.trackVisibility !== this.props.trackVisibility || prevProps.delay !== this.props.delay) { this.unobserve(); this.observeNode(); } } componentWillUnmount() { this.unobserve(); this.node = null; } observeNode() { if (!this.node || this.props.skip) return; const { threshold, root, rootMargin, trackVisibility, delay, fallbackInView } = this.props; this._unobserveCb = observe(this.node, this.handleChange, { threshold, root, rootMargin, // @ts-ignore trackVisibility, // @ts-ignore delay }, fallbackInView); } unobserve() { if (this._unobserveCb) { this._unobserveCb(); this._unobserveCb = null; } } render() { if (!isPlainChildren(this.props)) { const { inView, entry } = this.state; return this.props.children({ inView, entry, ref: this.handleNode }); } const _this$props = this.props, { children, as, tag } = _this$props, props = _objectWithoutPropertiesLoose(_this$props, _excluded); return /*#__PURE__*/React.createElement(as || tag || 'div', _extends({ ref: this.handleNode }, props), children); } } InView.displayName = 'InView'; InView.defaultProps = { threshold: 0, triggerOnce: false, initialInView: false }; /** * React Hooks make it easy to monitor the `inView` state of your components. Call * the `useInView` hook with the (optional) [options](#options) you need. It will * return an array containing a `ref`, the `inView` status and the current * [`entry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry). * Assign the `ref` to the DOM element you want to monitor, and the hook will * report the status. * * @example * ```jsx * import React from 'react'; * import { useInView } from 'react-intersection-observer'; * * const Component = () => { * const { ref, inView, entry } = useInView({ * threshold: 0, * }); * * return ( *
*

{`Header inside viewport ${inView}.`}

*
* ); * }; * ``` */ function useInView({ threshold, delay, trackVisibility, rootMargin, root, triggerOnce, skip, initialInView, fallbackInView } = {}) { const unobserve = React.useRef(); const [state, setState] = React.useState({ inView: !!initialInView }); const setRef = React.useCallback(node => { if (unobserve.current !== undefined) { unobserve.current(); unobserve.current = undefined; } // Skip creating the observer if (skip) return; if (node) { unobserve.current = observe(node, (inView, entry) => { setState({ inView, entry }); if (entry.isIntersecting && triggerOnce && unobserve.current) { // If it should only trigger once, unobserve the element after it's inView unobserve.current(); unobserve.current = undefined; } }, { root, rootMargin, threshold, // @ts-ignore trackVisibility, // @ts-ignore delay }, fallbackInView); } }, // We break the rule here, because we aren't including the actual `threshold` variable // eslint-disable-next-line react-hooks/exhaustive-deps [// If the threshold is an array, convert it to a string so it won't change between renders. // eslint-disable-next-line react-hooks/exhaustive-deps Array.isArray(threshold) ? threshold.toString() : threshold, root, rootMargin, triggerOnce, skip, trackVisibility, fallbackInView, delay]); /* eslint-disable-next-line */ useEffect(() => { if (!unobserve.current && state.entry && !triggerOnce && !skip) { // If we don't have a ref, then reset the state (unless the hook is set to only `triggerOnce` or `skip`) // This ensures we correctly reflect the current state - If you aren't observing anything, then nothing is inView setState({ inView: !!initialInView }); } }); const result = [setRef, state.inView, state.entry]; // Support object destructuring, by adding the specific values. result.ref = result[0]; result.inView = result[1]; result.entry = result[2]; return result; } export { InView, InView as default, defaultFallbackInView, observe, useInView }; //# sourceMappingURL=react-intersection-observer.esm.js.map