@tanstack/angular-virtual
Version:
Headless UI for virtualizing scrollable elements in Angular
135 lines (134 loc) • 4.26 kB
JavaScript
import { assertInInjectionContext, inject, Injector, runInInjectionContext, computed, untracked, linkedSignal, afterRenderEffect, ApplicationRef, DestroyRef } from "@angular/core";
import { Virtualizer, elementScroll, observeElementOffset, observeElementRect, windowScroll, observeWindowOffset, observeWindowRect } from "@tanstack/virtual-core";
export * from "@tanstack/virtual-core";
import { signalProxy } from "./proxy.js";
function injectScheduleDomFlushViaAppRefTick() {
const appRef = inject(ApplicationRef);
const destroyRef = inject(DestroyRef);
let hostDestroyed = false;
destroyRef.onDestroy(() => {
hostDestroyed = true;
});
let domFlushQueued = false;
return () => {
if (domFlushQueued) return;
domFlushQueued = true;
queueMicrotask(() => {
domFlushQueued = false;
if (hostDestroyed) return;
appRef.tick();
});
};
}
function injectVirtualizerBase(options, extensions = {}) {
let injector = extensions.injector;
if (!injector) {
assertInInjectionContext(injectVirtualizerBase);
injector = inject(Injector);
}
return runInInjectionContext(injector, () => {
const scheduleDomFlush = injectScheduleDomFlushViaAppRefTick();
const resolvedOptions = computed(() => {
const { useApplicationRefTick = true, ..._options } = options();
return {
..._options,
onChange: (instance, sync) => {
var _a;
reactiveVirtualizer.set(instance);
if (useApplicationRefTick) {
scheduleDomFlush();
}
(_a = _options.onChange) == null ? void 0 : _a.call(_options, instance, sync);
}
};
});
const lazyVirtualizer = computed(
() => new Virtualizer(untracked(resolvedOptions))
);
const reactiveVirtualizer = linkedSignal(
() => {
const virtualizer = lazyVirtualizer();
virtualizer.setOptions(resolvedOptions());
return virtualizer;
},
{ equal: () => false }
);
afterRenderEffect((cleanup) => {
cleanup(lazyVirtualizer()._didMount());
});
afterRenderEffect(() => {
reactiveVirtualizer()._willUpdate();
});
return signalProxy(
reactiveVirtualizer,
// Methods that pass through: call on the instance without tracking the signal read
[
"_didMount",
"_willUpdate",
"calculateRange",
"getVirtualIndexes",
"measure",
"measureElement",
"resizeItem",
"scrollBy",
"scrollToIndex",
"scrollToOffset",
"setOptions"
],
// Attributes that will be transformed to signals
[
"isScrolling",
"measurementsCache",
"options",
"range",
"scrollDirection",
"scrollElement",
"scrollOffset",
"scrollRect"
],
// Methods that will be tracked to the virtualizer signal
[
"getOffsetForAlignment",
"getOffsetForIndex",
"getVirtualItemForOffset",
"indexFromElement"
],
// Zero-arg methods exposed as computed signals
["getTotalSize", "getVirtualItems"]
// The rest is passed as is, and can be accessed or called before initialization
);
});
}
function injectVirtualizer(options) {
return injectVirtualizerBase(() => {
const _options = options();
return {
observeElementRect,
observeElementOffset,
scrollToFn: elementScroll,
getScrollElement: () => {
const elementOrRef = _options.scrollElement;
return (isElementRef(elementOrRef) ? elementOrRef.nativeElement : elementOrRef) ?? null;
},
..._options
};
});
}
function isElementRef(elementOrRef) {
return elementOrRef != null && "nativeElement" in elementOrRef;
}
function injectWindowVirtualizer(options) {
return injectVirtualizerBase(() => ({
getScrollElement: () => typeof document !== "undefined" ? window : null,
observeElementRect: observeWindowRect,
observeElementOffset: observeWindowOffset,
scrollToFn: windowScroll,
initialOffset: () => typeof document !== "undefined" ? window.scrollY : 0,
...options()
}));
}
export {
injectVirtualizer,
injectWindowVirtualizer
};
//# sourceMappingURL=index.js.map