UNPKG

@tamagui/react-native-web-lite

Version:
142 lines (141 loc) 6.13 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: !0 }); }, __copyProps = (to, from, except, desc) => { if (from && typeof from == "object" || typeof from == "function") for (let key of __getOwnPropNames(from)) !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); return to; }; var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod); var ViewabilityHelper_exports = {}; __export(ViewabilityHelper_exports, { default: () => ViewabilityHelper_default }); module.exports = __toCommonJS(ViewabilityHelper_exports); var import_react_native_web_internals = require("@tamagui/react-native-web-internals"); class ViewabilityHelper { _config; _hasInteracted = !1; _timers = /* @__PURE__ */new Set(); _viewableIndices = []; _viewableItems = /* @__PURE__ */new Map(); constructor(config = { viewAreaCoveragePercentThreshold: 0 }) { this._config = config; } /** * Cleanup, e.g. on unmount. Clears any pending timers. */ dispose() { this._timers.forEach(clearTimeout); } /** * Determines which items are viewable based on the current metrics and config. */ computeViewableItems(props, scrollOffset, viewportHeight, getFrameMetrics, renderRange) { const itemCount = props.getItemCount(props.data), { itemVisiblePercentThreshold, viewAreaCoveragePercentThreshold } = this._config, viewAreaMode = viewAreaCoveragePercentThreshold != null, viewablePercentThreshold = viewAreaMode ? viewAreaCoveragePercentThreshold : itemVisiblePercentThreshold; (0, import_react_native_web_internals.invariant)(viewablePercentThreshold != null && itemVisiblePercentThreshold != null != (viewAreaCoveragePercentThreshold != null), "Must set exactly one of itemVisiblePercentThreshold or viewAreaCoveragePercentThreshold"); const viewableIndices = []; if (itemCount === 0) return viewableIndices; let firstVisible = -1; const { first, last } = renderRange || { first: 0, last: itemCount - 1 }; if (last >= itemCount) return console.warn("Invalid render range computing viewability " + JSON.stringify({ renderRange, itemCount })), []; for (let idx = first; idx <= last; idx++) { const metrics = getFrameMetrics(idx, props); if (!metrics) continue; const top = metrics.offset - scrollOffset, bottom = top + metrics.length; if (top < viewportHeight && bottom > 0) firstVisible = idx, _isViewable(viewAreaMode, viewablePercentThreshold, top, bottom, viewportHeight, metrics.length) && viewableIndices.push(idx);else if (firstVisible >= 0) break; } return viewableIndices; } /** * Figures out which items are viewable and how that has changed from before and calls * `onViewableItemsChanged` as appropriate. */ onUpdate(props, scrollOffset, viewportHeight, getFrameMetrics, createViewToken, onViewableItemsChanged, renderRange) { const itemCount = props.getItemCount(props.data); if (this._config.waitForInteraction && !this._hasInteracted || itemCount === 0 || !getFrameMetrics(0, props)) return; let viewableIndices = []; if (itemCount && (viewableIndices = this.computeViewableItems(props, scrollOffset, viewportHeight, getFrameMetrics, renderRange)), !(this._viewableIndices.length === viewableIndices.length && this._viewableIndices.every((v, ii) => v === viewableIndices[ii]))) if (this._viewableIndices = viewableIndices, this._config.minimumViewTime) { const handle = setTimeout(() => { this._timers.delete(handle), this._onUpdateSync(props, viewableIndices, onViewableItemsChanged, createViewToken); }, this._config.minimumViewTime); this._timers.add(handle); } else this._onUpdateSync(props, viewableIndices, onViewableItemsChanged, createViewToken); } /** * clean-up cached _viewableIndices to evaluate changed items on next update */ resetViewableIndices() { this._viewableIndices = []; } /** * Records that an interaction has happened even if there has been no scroll. */ recordInteraction() { this._hasInteracted = !0; } _onUpdateSync(props, viewableIndicesToCheck, onViewableItemsChanged, createViewToken) { viewableIndicesToCheck = viewableIndicesToCheck.filter(ii => this._viewableIndices.includes(ii)); const prevItems = this._viewableItems, nextItems = new Map(viewableIndicesToCheck.map(ii => { const viewable = createViewToken(ii, !0, props); return [viewable.key, viewable]; })), changed = []; for (const [key, viewable] of nextItems) prevItems.has(key) || changed.push(viewable); for (const [key, viewable] of prevItems) nextItems.has(key) || changed.push({ ...viewable, isViewable: !1 }); changed.length > 0 && (this._viewableItems = nextItems, onViewableItemsChanged({ viewableItems: Array.from(nextItems.values()), changed, viewabilityConfig: this._config })); } } function _isViewable(viewAreaMode, viewablePercentThreshold, top, bottom, viewportHeight, itemLength) { if (_isEntirelyVisible(top, bottom, viewportHeight)) return !0; { const pixels = _getPixelsVisible(top, bottom, viewportHeight); return 100 * (viewAreaMode ? pixels / viewportHeight : pixels / itemLength) >= viewablePercentThreshold; } } function _getPixelsVisible(top, bottom, viewportHeight) { const visibleHeight = Math.min(bottom, viewportHeight) - Math.max(top, 0); return Math.max(0, visibleHeight); } function _isEntirelyVisible(top, bottom, viewportHeight) { return top >= 0 && bottom <= viewportHeight && bottom > top; } var ViewabilityHelper_default = ViewabilityHelper;