<commit_before>/*!
 * V4Fire Client Core
 * https://github.com/V4Fire/Client
 *
 * Released under the MIT license
 * https://github.com/V4Fire/Client/blob/master/LICENSE
 */

import { AsyncOpts } from 'core/async';
import symbolGenerator from 'core/symbol';

import Super from 'core/component/directives/in-view/modules/super';
import {

	ObservableElement,
	ObserveOptions,
	ObservableElementRect,
	ObservableElementsMap,
	IntersectionObserverOptions

} from 'core/component/directives/in-view/modules/meta';

import {

	hasMutationObserver,
	getRootRect,
	getElementRect,
	isElementInView,
	isElementVisible

} from 'core/component/directives/in-view/modules/mutation/helpers';

import { isInView } from 'core/component/directives/in-view/modules/helpers';

export const
	$$ = symbolGenerator();

export type AdapteeType = 'mutation';

export default class InView extends Super {
	/**
	 * Returns true if the current adaptee can be used
	 */
	static readonly acceptable: boolean = hasMutationObserver;

	/**
	 * Adaptee type
	 */
	readonly type: AdapteeType = 'mutation';

	/**
	 * Deferred variation of the recalculate function
	 * @see recalculate
	 */
	readonly recalculateDeffer: Function;

	/**
	 * Deferred variation of the check function
	 * @see recalculate
	 */
	readonly checkDeffer: Function;

	/**
	 * Mutation observer
	 */
	protected readonly mutationObserver: MutationObserver;

	/**
	 * Map contains an elements that needs to be poll
	 */
	protected readonly pollingElements: ObservableElementsMap = new Map();

	/**
	 * Contains an element position map
	 */
	protected map: Dictionary<ObservableElementRect[]> = {};

	/**
	 * Initializes an observer
	 */
	constructor() {
		super();

		const
			RECALCULATE_TIMEOUT = 100,
			POLL_INTERVAL = 75,
			CHECK_TIMEOUT = 50;

		const
			{async: $a} = this;

		const checkDeffer = () => $a.setTimeout(() => this.check(), CHECK_TIMEOUT, {
			group: 'inView',
			label: $$.check,
			join: true
		});

		const recalculateDeffer = (opts?: AsyncOpts) => $a.setTimeout(() => this.recalculate(), RECALCULATE_TIMEOUT, {
			group: 'inView',
			label: $$.recalculate,
			join: true,
			...opts
		});

		this.checkDeffer = checkDeffer;
		this.recalculateDeffer = recalculateDeffer;

		this.mutationObserver = new MutationObserver(() => {
			this.recalculateDeffer();
		});

		this.mutationObserver.observe(document.body, {
			childList: true,
			attributes: true,
			subtree: true,
			characterData: true
		});

		$a.setInterval(this.poll, POLL_INTERVAL, {
			group: 'inView',
			label: $$.poll,
			join: true
		});

		$a.on(document, 'scroll', checkDeffer);
		$a.on(window, 'resize', () => recalculateDeffer({
			join: false
		}));
	}

	/** @override */
	observe(el: HTMLElement, options: IntersectionObserverOptions & ObserveOptions): ObservableElement | false {
		const
			observable = super.observe(el, options);

		if (!observable) {
			return false;
		}

		const
			{async: $a} = this;

		if (observable.handleTransitionEnd) {
			$a.on(el, 'transitionend', this.recalculateDeffer, {
				group: 'inView',
				label: `transitionend-${el.id}`
			});
		}

		return observable;
	}

	/**
	 * Removes an element from observable elements
	 * @param el
	 */
	remove(el: HTMLElement): boolean {
		return this.getElMap(el).delete(el);
	}

	/**
	 * Stops observes an element
	 * @param el
	 */
	stopObserve(el: HTMLElement): boolean {
		const
			observable = this.get(el);

		let
			res = true;

		if (!observable) {
			return false;
		}

		this.clearAllAsync(observable);

		if (observable.removeStrategy === 'remove') {
			res = this.remove(el);

		} else {
			observable.isDeactivated = true;
		}

		if (!observable.polling) {
			this.recalculateDeffer();
		}

		return res;
	}

	/**
	 * Polls elements
	 */
	poll(): void {
		this.pollingElements.forEach((el) => {
			const
				isElementIn = isInView(el.node, el.threshold);

			if (isElementIn && !el.isLeaving) {
				this.onObservableIn(el);

			} else if (!isElementIn) {
				this.onObservableOut(el);
			}
		});
	}

	/**
	 * Checks if elements is in view
	 */
	check(): void {
		const
			rootRect = getRootRect();

		const
			checkRangeTo = Math.ceil((rootRect.height + rootRect.scrollTop) / 100) + 1,
			checkRangeFrom = Math.ceil(rootRect.scrollTop / 100);

		let
			start = checkRangeFrom - 1 >= 0 ? 0 : checkRangeFrom - 1;

		while (start !== checkRangeTo) {
			const<commit_msg>Fix issue with deactivated element being polled<commit_after>/*!
 * V4Fire Client Core
 * https://github.com/V4Fire/Client
 *
 * Released under the MIT license
 * https://github.com/V4Fire/Client/blob/master/LICENSE
 */

import { AsyncOpts } from 'core/async';
import symbolGenerator from 'core/symbol';

import Super from 'core/component/directives/in-view/modules/super';
import {

	ObservableElement,
	ObserveOptions,
	ObservableElementRect,
	ObservableElementsMap,
	IntersectionObserverOptions

} from 'core/component/directives/in-view/modules/meta';

import {

	hasMutationObserver,
	getRootRect,
	getElementRect,
	isElementInView,
	isElementVisible

} from 'core/component/directives/in-view/modules/mutation/helpers';

import { isInView } from 'core/component/directives/in-view/modules/helpers';

export const
	$$ = symbolGenerator();

export type AdapteeType = 'mutation';

export default class InView extends Super {
	/**
	 * Returns true if the current adaptee can be used
	 */
	static readonly acceptable: boolean = hasMutationObserver;

	/**
	 * Adaptee type
	 */
	readonly type: AdapteeType = 'mutation';

	/**
	 * Deferred variation of the recalculate function
	 * @see recalculate
	 */
	readonly recalculateDeffer: Function;

	/**
	 * Deferred variation of the check function
	 * @see recalculate
	 */
	readonly checkDeffer: Function;

	/**
	 * Mutation observer
	 */
	protected readonly mutationObserver: MutationObserver;

	/**
	 * Map contains an elements that needs to be poll
	 */
	protected readonly pollingElements: ObservableElementsMap = new Map();

	/**
	 * Contains an element position map
	 */
	protected map: Dictionary<ObservableElementRect[]> = {};

	/**
	 * Initializes an observer
	 */
	constructor() {
		super();

		const
			RECALCULATE_TIMEOUT = 100,
			POLL_INTERVAL = 75,
			CHECK_TIMEOUT = 50;

		const
			{async: $a} = this;

		const checkDeffer = () => $a.setTimeout(() => this.check(), CHECK_TIMEOUT, {
			group: 'inView',
			label: $$.check,
			join: true
		});

		const recalculateDeffer = (opts?: AsyncOpts) => $a.setTimeout(() => this.recalculate(), RECALCULATE_TIMEOUT, {
			group: 'inView',
			label: $$.recalculate,
			join: true,
			...opts
		});

		this.checkDeffer = checkDeffer;
		this.recalculateDeffer = recalculateDeffer;

		this.mutationObserver = new MutationObserver(() => {
			this.recalculateDeffer();
		});

		this.mutationObserver.observe(document.body, {
			childList: true,
			attributes: true,
			subtree: true,
			characterData: true
		});

		$a.setInterval(this.poll, POLL_INTERVAL, {
			group: 'inView',
			label: $$.poll,
			join: true
		});

		$a.on(document, 'scroll', checkDeffer);
		$a.on(window, 'resize', () => recalculateDeffer({
			join: false
		}));
	}

	/** @override */
	observe(el: HTMLElement, options: IntersectionObserverOptions & ObserveOptions): ObservableElement | false {
		const
			observable = super.observe(el, options);

		if (!observable) {
			return false;
		}

		const
			{async: $a} = this;

		if (observable.handleTransitionEnd) {
			$a.on(el, 'transitionend', this.recalculateDeffer, {
				group: 'inView',
				label: `transitionend-${el.id}`
			});
		}

		return observable;
	}

	/**
	 * Removes an element from observable elements
	 * @param el
	 */
	remove(el: HTMLElement): boolean {
		return this.getElMap(el).delete(el);
	}

	/**
	 * Stops observes an element
	 * @param el
	 */
	stopObserve(el: HTMLElement): boolean {
		const
			observable = this.get(el);

		let
			res = true;

		if (!observable) {
			return false;
		}

		this.clearAllAsync(observable);

		if (observable.removeStrategy === 'remove') {
			res = this.remove(el);

		} else {
			observable.isDeactivated = true;
		}

		if (!observable.polling) {
			this.recalculateDeffer();
		}

		return res;
	}

	/**
	 * Polls elements
	 */
	poll(): void {
		this.pollingElements.forEach((el) => {
			if (el.isDeactivated) {
				return;
			}

			const
				isElementIn = isInView(el.node, el.threshold);

			if (isElementIn && !el.isLeaving) {
				this.onObservableIn(el);

			} else if (!isElementIn) {
				this.onObservableOut(el);
			}
		});
	}

	/**
	 * Checks if elements is in view
	 */
	check(): void {
		const
			rootRect = getRootRect();

		const
			checkRangeTo = Math.ceil((rootRect.height + rootRect.scrollTop) / 100) + 1,
			checkRangeFrom = Math.ceil(rootRect.scrollTop / 100);

		let
			start = checkRangeFrom - 1 >= 0 ? 0 : checkRangeFrom - 1;

		while (start !== checkRangeTo) {
			const
{'fraction_non_alphanumeric': 0.1354131343958404, 'fraction_numerical': 0.004747371990505256, 'mean_word_length': 3.482269503546099, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 21, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3749764', 'n_tokens_mistral': 3391, 'n_tokens_neox': 2888, 'n_words': 662}