"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const utils = require("./utils.cjs"); const defaultKeyExtractor = (index) => index; const defaultRangeExtractor = (range) => { const start = Math.max(range.startIndex - range.overscan, 0); const end = Math.min(range.endIndex + range.overscan, range.count - 1); const arr = []; for (let i = start; i <= end; i++) { arr.push(i); } return arr; }; const observeElementRect = (instance, cb) => { const element = instance.scrollElement; if (!element) { return; } const targetWindow = instance.targetWindow; if (!targetWindow) { return; } const handler = (rect) => { const { width, height } = rect; cb({ width: Math.round(width), height: Math.round(height) }); }; handler(element.getBoundingClientRect()); if (!targetWindow.ResizeObserver) { return () => { }; } const observer = new targetWindow.ResizeObserver((entries) => { const entry = entries[0]; if (entry == null ? void 0 : entry.borderBoxSize) { const box = entry.borderBoxSize[0]; if (box) { handler({ width: box.inlineSize, height: box.blockSize }); return; } } handler(element.getBoundingClientRect()); }); observer.observe(element, { box: "border-box" }); return () => { observer.unobserve(element); }; }; const addEventListenerOptions = { passive: true }; const observeWindowRect = (instance, cb) => { const element = instance.scrollElement; if (!element) { return; } const handler = () => { cb({ width: element.innerWidth, height: element.innerHeight }); }; handler(); element.addEventListener("resize", handler, addEventListenerOptions); return () => { element.removeEventListener("resize", handler); }; }; const supportsScrollend = typeof window == "undefined" ? true : "onscrollend" in window; const observeElementOffset = (instance, cb) => { const element = instance.scrollElement; if (!element) { return; } const targetWindow = instance.targetWindow; if (!targetWindow) { return; } let offset = 0; const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : utils.debounce( targetWindow, () => { cb(offset, false); }, instance.options.isScrollingResetDelay ); const createHandler = (isScrolling) => () => { const { horizontal, isRtl } = instance.options; offset = horizontal ? element["scrollLeft"] * (isRtl && -1 || 1) : element["scrollTop"]; fallback(); cb(offset, isScrolling); }; const handler = createHandler(true); const endHandler = createHandler(false); endHandler(); element.addEventListener("scroll", handler, addEventListenerOptions); element.addEventListener("scrollend", endHandler, addEventListenerOptions); return () => { element.removeEventListener("scroll", handler); element.removeEventListener("scrollend", endHandler); }; }; const observeWindowOffset = (instance, cb) => { const element = instance.scrollElement; if (!element) { return; } const targetWindow = instance.targetWindow; if (!targetWindow) { return; } let offset = 0; const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : utils.debounce( targetWindow, () => { cb(offset, false); }, instance.options.isScrollingResetDelay ); const createHandler = (isScrolling) => () => { offset = element[instance.options.horizontal ? "scrollX" : "scrollY"]; fallback(); cb(offset, isScrolling); }; const handler = createHandler(true); const endHandler = createHandler(false); endHandler(); element.addEventListener("scroll", handler, addEventListenerOptions); element.addEventListener("scrollend", endHandler, addEventListenerOptions); return () => { element.removeEventListener("scroll", handler); element.removeEventListener("scrollend", endHandler); }; }; const measureElement = (element, entry, instance) => { if (entry == null ? void 0 : entry.borderBoxSize) { const box = entry.borderBoxSize[0]; if (box) { const size = Math.round( box[instance.options.horizontal ? "inlineSize" : "blockSize"] ); return size; } } return Math.round( element.getBoundingClientRect()[instance.options.horizontal ? "width" : "height"] ); }; const windowScroll = (offset, { adjustments = 0, behavior }, instance) => { var _a, _b; const toOffset = offset + adjustments; (_b = (_a = instance.scrollElement) == null ? void 0 : _a.scrollTo) == null ? void 0 : _b.call(_a, { [instance.options.horizontal ? "left" : "top"]: toOffset, behavior }); }; const elementScroll = (offset, { adjustments = 0, behavior }, instance) => { var _a, _b; const toOffset = offset + adjustments; (_b = (_a = instance.scrollElement) == null ? void 0 : _a.scrollTo) == null ? void 0 : _b.call(_a, { [instance.options.horizontal ? "left" : "top"]: toOffset, behavior }); }; class Virtualizer { constructor(opts) { this.unsubs = []; this.scrollElement = null; this.targetWindow = null; this.isScrolling = false; this.scrollToIndexTimeoutId = null; this.measurementsCache = []; this.itemSizeCache = /* @__PURE__ */ new Map(); this.pendingMeasuredCacheIndexes = []; this.scrollRect = null; this.scrollOffset = null; this.scrollDirection = null; this.scrollAdjustments = 0; this.elementsCache = /* @__PURE__ */ new Map(); this.observer = /* @__PURE__ */ (() => { let _ro = null; const get = () => { if (_ro) { return _ro; } if (!this.targetWindow || !this.targetWindow.ResizeObserver) { return null; } return _ro = new this.targetWindow.ResizeObserver((entries) => { entries.forEach((entry) => { this._measureElement(entry.target, entry); }); }); }; return { disconnect: () => { var _a; (_a = get()) == null ? void 0 : _a.disconnect(); _ro = null; }, observe: (target) => { var _a; return (_a = get()) == null ? void 0 : _a.observe(target, { box: "border-box" }); }, unobserve: (target) => { var _a; return (_a = get()) == null ? void 0 : _a.unobserve(target); } }; })(); this.range = null; this.setOptions = (opts2) => { Object.entries(opts2).forEach(([key, value]) => { if (typeof value === "undefined") delete opts2[key]; }); this.options = { debug: false, initialOffset: 0, overscan: 1, paddingStart: 0, paddingEnd: 0, scrollPaddingStart: 0, scrollPaddingEnd: 0, horizontal: false, getItemKey: defaultKeyExtractor, rangeExtractor: defaultRangeExtractor, onChange: () => { }, measureElement, initialRect: { width: 0, height: 0 }, scrollMargin: 0, gap: 0, indexAttribute: "data-index", initialMeasurementsCache: [], lanes: 1, isScrollingResetDelay: 150, enabled: true, isRtl: false, useScrollendEvent: true, ...opts2 }; }; this.notify = (sync) => { var _a, _b; (_b = (_a = this.options).onChange) == null ? void 0 : _b.call(_a, this, sync); }; this.maybeNotify = utils.memo( () => { this.calculateRange(); return [ this.isScrolling, this.range ? this.range.startIndex : null, this.range ? this.range.endIndex : null ]; }, (isScrolling) => { this.notify(isScrolling); }, { key: process.env.NODE_ENV !== "production" && "maybeNotify", debug: () => this.options.debug, initialDeps: [ this.isScrolling, this.range ? this.range.startIndex : null, this.range ? this.range.endIndex : null ] } ); this.cleanup = () => { this.unsubs.filter(Boolean).forEach((d) => d()); this.unsubs = []; this.observer.disconnect(); this.scrollElement = null; this.targetWindow = null; }; this._didMount = () => { return () => { this.cleanup(); }; }; this._willUpdate = () => { var _a; const scrollElement = this.options.enabled ? this.options.getScrollElement() : null; if (this.scrollElement !== scrollElement) { this.cleanup(); if (!scrollElement) { this.maybeNotify(); return; } this.scrollElement = scrollElement; if (this.scrollElement && "ownerDocument" in this.scrollElement) { this.targetWindow = this.scrollElement.ownerDocument.defaultView; } else { this.targetWindow = ((_a = this.scrollElement) == null ? void 0 : _a.window) ?? null; } this.elementsCache.forEach((cached) => { this.observer.observe(cached); }); this._scrollToOffset(this.getScrollOffset(), { adjustments: void 0, behavior: void 0 }); this.unsubs.push( this.options.observeElementRect(this, (rect) => { this.scrollRect = rect; this.maybeNotify(); }) ); this.unsubs.push( this.options.observeElementOffset(this, (offset, isScrolling) => { this.scrollAdjustments = 0; this.scrollDirection = isScrolling ? this.getScrollOffset() < offset ? "forward" : "backward" : null; this.scrollOffset = offset; this.isScrolling = isScrolling; this.maybeNotify(); }) ); } }; this.getSize = () => { if (!this.options.enabled) { this.scrollRect = null; return 0; } this.scrollRect = this.scrollRect ?? this.options.initialRect; return this.scrollRect[this.options.horizontal ? "width" : "height"]; }; this.getScrollOffset = () => { if (!this.options.enabled) { this.scrollOffset = null; return 0; } this.scrollOffset = this.scrollOffset ?? (typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset); return this.scrollOffset; }; this.getFurthestMeasurement = (measurements, index) => { const furthestMeasurementsFound = /* @__PURE__ */ new Map(); const furthestMeasurements = /* @__PURE__ */ new Map(); for (let m = index - 1; m >= 0; m--) { const measurement = measurements[m]; if (furthestMeasurementsFound.has(measurement.lane)) { continue; } const previousFurthestMeasurement = furthestMeasurements.get( measurement.lane ); if (previousFurthestMeasurement == null || measurement.end > previousFurthestMeasurement.end) { furthestMeasurements.set(measurement.lane, measurement); } else if (measurement.end < previousFurthestMeasurement.end) { furthestMeasurementsFound.set(measurement.lane, true); } if (furthestMeasurementsFound.size === this.options.lanes) { break; } } return furthestMeasurements.size === this.options.lanes ? Array.from(furthestMeasurements.values()).sort((a, b) => { if (a.end === b.end) { return a.index - b.index; } return a.end - b.end; })[0] : void 0; }; this.getMeasurementOptions = utils.memo( () => [ this.options.count, this.options.paddingStart, this.options.scrollMargin, this.options.getItemKey, this.options.enabled ], (count, paddingStart, scrollMargin, getItemKey, enabled) => { this.pendingMeasuredCacheIndexes = []; return { count, paddingStart, scrollMargin, getItemKey, enabled }; }, { key: false } ); this.getMeasurements = utils.memo( () => [this.getMeasurementOptions(), this.itemSizeCache], ({ count, paddingStart, scrollMargin, getItemKey, enabled }, itemSizeCache) => { if (!enabled) { this.measurementsCache = []; this.itemSizeCache.clear(); return []; } if (this.measurementsCache.length === 0) { this.measurementsCache = this.options.initialMeasurementsCache; this.measurementsCache.forEach((item) => { this.itemSizeCache.set(item.key, item.size); }); } const min = this.pendingMeasuredCacheIndexes.length > 0 ? Math.min(...this.pendingMeasuredCacheIndexes) : 0; this.pendingMeasuredCacheIndexes = []; const measurements = this.measurementsCache.slice(0, min); for (let i = min; i < count; i++) { const key = getItemKey(i); const furthestMeasurement = this.options.lanes === 1 ? measurements[i - 1] : this.getFurthestMeasurement(measurements, i); const start = furthestMeasurement ? furthestMeasurement.end + this.options.gap : paddingStart + scrollMargin; const measuredSize = itemSizeCache.get(key); const size = typeof measuredSize === "number" ? measuredSize : this.options.estimateSize(i); const end = start + size; const lane = furthestMeasurement ? furthestMeasurement.lane : i % this.options.lanes; measurements[i] = { index: i, start, size, end, key, lane }; } this.measurementsCache = measurements; return measurements; }, { key: process.env.NODE_ENV !== "production" && "getMeasurements", debug: () => this.options.debug } ); this.calculateRange = utils.memo( () => [this.getMeasurements(), this.getSize(), this.getScrollOffset()], (measurements, outerSize, scrollOffset) => { return this.range = measurements.length > 0 && outerSize > 0 ? calculateRange({ measurements, outerSize, scrollOffset }) : null; }, { key: process.env.NODE_ENV !== "production" && "calculateRange", debug: () => this.options.debug } ); this.getIndexes = utils.memo( () => [ this.options.rangeExtractor, this.calculateRange(), this.options.overscan, this.options.count ], (rangeExtractor, range, overscan, count) => { return range === null ? [] : rangeExtractor({ startIndex: range.startIndex, endIndex: range.endIndex, overscan, count }); }, { key: process.env.NODE_ENV !== "production" && "getIndexes", debug: () => this.options.debug } ); this.indexFromElement = (node) => { const attributeName = this.options.indexAttribute; const indexStr = node.getAttribute(attributeName); if (!indexStr) { console.warn( `Missing attribute name '${attributeName}={index}' on measured element.` ); return -1; } return parseInt(indexStr, 10); }; this._measureElement = (node, entry) => { const index = this.indexFromElement(node); const item = this.measurementsCache[index]; if (!item) { return; } const key = item.key; const prevNode = this.elementsCache.get(key); if (prevNode !== node) { if (prevNode) { this.observer.unobserve(prevNode); } this.observer.observe(node); this.elementsCache.set(key, node); } if (node.isConnected) { this.resizeItem(index, this.options.measureElement(node, entry, this)); } }; this.resizeItem = (index, size) => { const item = this.measurementsCache[index]; if (!item) { return; } const itemSize = this.itemSizeCache.get(item.key) ?? item.size; const delta = size - itemSize; if (delta !== 0) { if (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange(item, delta, this) : item.start < this.getScrollOffset() + this.scrollAdjustments) { if (process.env.NODE_ENV !== "production" && this.options.debug) { console.info("correction", delta); } this._scrollToOffset(this.getScrollOffset(), { adjustments: this.scrollAdjustments += delta, behavior: void 0 }); } this.pendingMeasuredCacheIndexes.push(item.index); this.itemSizeCache = new Map(this.itemSizeCache.set(item.key, size)); this.notify(false); } }; this.measureElement = (node) => { if (!node) { this.elementsCache.forEach((cached, key) => { if (!cached.isConnected) { this.observer.unobserve(cached); this.elementsCache.delete(key); } }); return; } this._measureElement(node, void 0); }; this.getVirtualItems = utils.memo( () => [this.getIndexes(), this.getMeasurements()], (indexes, measurements) => { const virtualItems = []; for (let k = 0, len = indexes.length; k < len; k++) { const i = indexes[k]; const measurement = measurements[i]; virtualItems.push(measurement); } return virtualItems; }, { key: process.env.NODE_ENV !== "production" && "getVirtualItems", debug: () => this.options.debug } ); this.getVirtualItemForOffset = (offset) => { const measurements = this.getMeasurements(); if (measurements.length === 0) { return void 0; } return utils.notUndefined( measurements[findNearestBinarySearch( 0, measurements.length - 1, (index) => utils.notUndefined(measurements[index]).start, offset )] ); }; this.getOffsetForAlignment = (toOffset, align) => { const size = this.getSize(); const scrollOffset = this.getScrollOffset(); if (align === "auto") { if (toOffset <= scrollOffset) { align = "start"; } else if (toOffset >= scrollOffset + size) { align = "end"; } else { align = "start"; } } if (align === "start") { toOffset = toOffset; } else if (align === "end") { toOffset = toOffset - size; } else if (align === "center") { toOffset = toOffset - size / 2; } const scrollSizeProp = this.options.horizontal ? "scrollWidth" : "scrollHeight"; const scrollSize = this.scrollElement ? "document" in this.scrollElement ? this.scrollElement.document.documentElement[scrollSizeProp] : this.scrollElement[scrollSizeProp] : 0; const maxOffset = scrollSize - size; return Math.max(Math.min(maxOffset, toOffset), 0); }; this.getOffsetForIndex = (index, align = "auto") => { index = Math.max(0, Math.min(index, this.options.count - 1)); const item = this.measurementsCache[index]; if (!item) { return void 0; } const size = this.getSize(); const scrollOffset = this.getScrollOffset(); if (align === "auto") { if (item.end >= scrollOffset + size - this.options.scrollPaddingEnd) { align = "end"; } else if (item.start <= scrollOffset + this.options.scrollPaddingStart) { align = "start"; } else { return [scrollOffset, align]; } } const toOffset = align === "end" ? item.end + this.options.scrollPaddingEnd : item.start - this.options.scrollPaddingStart; return [this.getOffsetForAlignment(toOffset, align), align]; }; this.isDynamicMode = () => this.elementsCache.size > 0; this.cancelScrollToIndex = () => { if (this.scrollToIndexTimeoutId !== null && this.targetWindow) { this.targetWindow.clearTimeout(this.scrollToIndexTimeoutId); this.scrollToIndexTimeoutId = null; } }; this.scrollToOffset = (toOffset, { align = "start", behavior } = {}) => { this.cancelScrollToIndex(); if (behavior === "smooth" && this.isDynamicMode()) { console.warn( "The `smooth` scroll behavior is not fully supported with dynamic size." ); } this._scrollToOffset(this.getOffsetForAlignment(toOffset, align), { adjustments: void 0, behavior }); }; this.scrollToIndex = (index, { align: initialAlign = "auto", behavior } = {}) => { index = Math.max(0, Math.min(index, this.options.count - 1)); this.cancelScrollToIndex(); if (behavior === "smooth" && this.isDynamicMode()) { console.warn( "The `smooth` scroll behavior is not fully supported with dynamic size." ); } const offsetAndAlign = this.getOffsetForIndex(index, initialAlign); if (!offsetAndAlign) return; const [offset, align] = offsetAndAlign; this._scrollToOffset(offset, { adjustments: void 0, behavior }); if (behavior !== "smooth" && this.isDynamicMode() && this.targetWindow) { this.scrollToIndexTimeoutId = this.targetWindow.setTimeout(() => { this.scrollToIndexTimeoutId = null; const elementInDOM = this.elementsCache.has( this.options.getItemKey(index) ); if (elementInDOM) { const [latestOffset] = utils.notUndefined( this.getOffsetForIndex(index, align) ); if (!utils.approxEqual(latestOffset, this.getScrollOffset())) { this.scrollToIndex(index, { align, behavior }); } } else { this.scrollToIndex(index, { align, behavior }); } }); } }; this.scrollBy = (delta, { behavior } = {}) => { this.cancelScrollToIndex(); if (behavior === "smooth" && this.isDynamicMode()) { console.warn( "The `smooth` scroll behavior is not fully supported with dynamic size." ); } this._scrollToOffset(this.getScrollOffset() + delta, { adjustments: void 0, behavior }); }; this.getTotalSize = () => { var _a; const measurements = this.getMeasurements(); let end; if (measurements.length === 0) { end = this.options.paddingStart; } else { end = this.options.lanes === 1 ? ((_a = measurements[measurements.length - 1]) == null ? void 0 : _a.end) ?? 0 : Math.max( ...measurements.slice(-this.options.lanes).map((m) => m.end) ); } return Math.max( end - this.options.scrollMargin + this.options.paddingEnd, 0 ); }; this._scrollToOffset = (offset, { adjustments, behavior }) => { this.options.scrollToFn(offset, { behavior, adjustments }, this); }; this.measure = () => { this.itemSizeCache = /* @__PURE__ */ new Map(); this.notify(false); }; this.setOptions(opts); } } const findNearestBinarySearch = (low, high, getCurrentValue, value) => { while (low <= high) { const middle = (low + high) / 2 | 0; const currentValue = getCurrentValue(middle); if (currentValue < value) { low = middle + 1; } else if (currentValue > value) { high = middle - 1; } else { return middle; } } if (low > 0) { return low - 1; } else { return 0; } }; function calculateRange({ measurements, outerSize, scrollOffset }) { const count = measurements.length - 1; const getOffset = (index) => measurements[index].start; const startIndex = findNearestBinarySearch(0, count, getOffset, scrollOffset); let endIndex = startIndex; while (endIndex < count && measurements[endIndex].end < scrollOffset + outerSize) { endIndex++; } return { startIndex, endIndex }; } exports.approxEqual = utils.approxEqual; exports.debounce = utils.debounce; exports.memo = utils.memo; exports.notUndefined = utils.notUndefined; exports.Virtualizer = Virtualizer; exports.defaultKeyExtractor = defaultKeyExtractor; exports.defaultRangeExtractor = defaultRangeExtractor; exports.elementScroll = elementScroll; exports.measureElement = measureElement; exports.observeElementOffset = observeElementOffset; exports.observeElementRect = observeElementRect; exports.observeWindowOffset = observeWindowOffset; exports.observeWindowRect = observeWindowRect; exports.windowScroll = windowScroll; //# sourceMappingURL=index.cjs.map