/** * @summary is-in-viewport-mixin * @version 3.0.0 * @since 1.0.0 * @author Arian Khosravi <arian.khosravi@aofl.com> */ import {isInViewport} from './is-in-viewport'; /** * Mixes the superClass with functions necessary to detect if the element * is within the visible area of the page. * * @memberof module:@aofl/component-utils * @param {LitElement} superClass */ const isInViewportMixin = (superClass) => { /** * @memberof module:@aofl/component-utils * @extends {superClass} */ class IsInViewportMixinClass extends superClass { /** * Creates an instance of IsInViewportMixinClass. * @param {*} args */ constructor(...args) { super(...args); this.trackScrollHosts = [window]; this.isWithinViewport; this.onceWithinViewport = false; this.checkInViewport = this.checkInViewport.bind(this); } /** * * @param {*} args */ connectedCallback(...args) { super.connectedCallback(...args); this.addListeners(); } /** * */ checkInViewport() { const oldIsWithinViewport = this.isWithinViewport; this.isWithinViewport = this.offsetHeight > 0 && this.offsetWidth > 0 && isInViewport(this, this.widthThreshold, this.heightThreshold); if (!this.onceWithinViewport && this.isWithinViewport === true) { this.onceWithinViewport = true; this.firstWithinViewport(); } if (this.isWithinViewport !== oldIsWithinViewport) { this.withinViewportUpdated(this.isWithinViewport, oldIsWithinViewport); } } /** * */ firstUpdated() { this.checkInViewport(); } /** * */ addListeners() { let parent = this; /* istanbul ignore next */ while (parent !== null) { if (parent.assignedSlot) { parent = parent.assignedSlot; } else if (typeof parent.tagName === 'undefined' && typeof parent.host !== 'undefined') { this.trackScrollHosts.push(parent); parent = parent.host; } else if (parent.parentNode) { parent = parent.parentNode; } else { break; } } window.addEventListener('resize', this.checkInViewport); for (let i = 0; i < this.trackScrollHosts.length; i++) { this.trackScrollHosts[i].addEventListener('scroll', this.checkInViewport); } } /* istanbul ignore next */ /** * firstWithinViewport() is invoked when the element scrolls into view for the first time. This * function should be implemented by the sub class. * * @protected */ firstWithinViewport() { } /* istanbul ignore next */ /** * withinViewportUpdated() is invoked anytime the element enters or exists the viewport. This * function should be implemented by the sub class. * * @protected * @param {Boolean} newValue * @param {Boolean} oldValue */ withinViewportUpdated() {} /** * When stopiIsInViewportCheck() is invoked it removes the event listeners and stops invoking * the withinViewportUpdated() function. This is useful when we want to disconnect the event * listeners and keep the component attached to dom. For example, consider lazy loading images * with aofl-img. Once the image is loaded it is no longer necessary to check isInViewStatus. * * @protected */ stopIsInViewportCheck() { window.removeEventListener('resize', this.checkInViewport); for (let i = 0; i < this.trackScrollHosts.length; i++) { this.trackScrollHosts[i].removeEventListener('scroll', this.checkInViewport); } } /** * * * @param {*} args */ disconnectedCallback(...args) { this.stopIsInViewportCheck(); super.disconnectedCallback(...args); } } return IsInViewportMixinClass; }; export { isInViewportMixin };