@tanstack/angular-virtual
Version:
Headless UI for virtualizing scrollable elements in Angular
1 lines • 9.31 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../../src/index.ts"],"sourcesContent":["import {\n ApplicationRef,\n DestroyRef,\n Injector,\n afterRenderEffect,\n assertInInjectionContext,\n computed,\n inject,\n linkedSignal,\n runInInjectionContext,\n untracked,\n} from '@angular/core'\nimport {\n Virtualizer,\n elementScroll,\n observeElementOffset,\n observeElementRect,\n observeWindowOffset,\n observeWindowRect,\n windowScroll,\n} from '@tanstack/virtual-core'\nimport { signalProxy } from './proxy'\nimport type { ElementRef } from '@angular/core'\nimport type { PartialKeys, VirtualizerOptions } from '@tanstack/virtual-core'\nimport type { AngularVirtualizer } from './types'\n\nexport * from '@tanstack/virtual-core'\nexport * from './types'\n\nexport type AngularVirtualizerOptions<\n TScrollElement extends Element | Window,\n TItemElement extends Element,\n> = VirtualizerOptions<TScrollElement, TItemElement> & {\n /**\n * Whether to flush the DOM using `ApplicationRef.tick()`\n * @default true\n * */\n useApplicationRefTick?: boolean\n}\n\nexport type AngularExtensionOptions = {\n /**\n * The injector to use for the virtualizer.\n * @default inject(Injector)\n */\n injector?: Injector\n}\n\n// Flush CD after virtual-core updates so template bindings hit the DOM\n// before the next frame's scroll reconciliation reads `scrollHeight`.\nfunction injectScheduleDomFlushViaAppRefTick() {\n const appRef = inject(ApplicationRef)\n const destroyRef = inject(DestroyRef)\n let hostDestroyed = false\n destroyRef.onDestroy(() => {\n hostDestroyed = true\n })\n let domFlushQueued = false\n\n return () => {\n if (domFlushQueued) return\n domFlushQueued = true\n queueMicrotask(() => {\n domFlushQueued = false\n if (hostDestroyed) return\n appRef.tick()\n })\n }\n}\n\nfunction injectVirtualizerBase<\n TScrollElement extends Element | Window,\n TItemElement extends Element,\n>(\n options: () => AngularVirtualizerOptions<TScrollElement, TItemElement>,\n extensions: AngularExtensionOptions = {},\n) {\n let injector = extensions.injector\n if (!injector) {\n assertInInjectionContext(injectVirtualizerBase)\n injector = inject(Injector)\n }\n\n return runInInjectionContext(injector, () => {\n const scheduleDomFlush = injectScheduleDomFlushViaAppRefTick()\n\n const resolvedOptions = computed<\n VirtualizerOptions<TScrollElement, TItemElement>\n >(() => {\n const { useApplicationRefTick = true, ..._options } = options()\n return {\n ..._options,\n onChange: (instance, sync) => {\n reactiveVirtualizer.set(instance)\n if (useApplicationRefTick) {\n scheduleDomFlush()\n }\n _options.onChange?.(instance, sync)\n },\n }\n })\n\n // Computed here is used to lazily initialize the Virtualizer instance,\n // allowing it to be created after input/model signals are initialized.\n // Options are untracked to maintain a single instance of the Virtualizer.\n const lazyVirtualizer = computed(\n () => new Virtualizer(untracked(resolvedOptions)),\n )\n\n // The reference in onChange is safe since computed signals are not evaluated eagerly.\n const reactiveVirtualizer = linkedSignal(\n () => {\n const virtualizer = lazyVirtualizer()\n // If setOptions does not call onChange, it's safe to call it here\n virtualizer.setOptions(resolvedOptions())\n return virtualizer\n },\n { equal: () => false },\n )\n\n afterRenderEffect((cleanup) => {\n cleanup(lazyVirtualizer()._didMount())\n })\n\n afterRenderEffect(() => {\n reactiveVirtualizer()._willUpdate()\n })\n\n return signalProxy(\n reactiveVirtualizer,\n // Methods that pass through: call on the instance without tracking the signal read\n [\n '_didMount',\n '_willUpdate',\n 'calculateRange',\n 'getVirtualIndexes',\n 'measure',\n 'measureElement',\n 'resizeItem',\n 'scrollBy',\n 'scrollToIndex',\n 'scrollToOffset',\n 'setOptions',\n ],\n // Attributes that will be transformed to signals\n [\n 'isScrolling',\n 'measurementsCache',\n 'options',\n 'range',\n 'scrollDirection',\n 'scrollElement',\n 'scrollOffset',\n 'scrollRect',\n ],\n // Methods that will be tracked to the virtualizer signal\n [\n 'getOffsetForAlignment',\n 'getOffsetForIndex',\n 'getVirtualItemForOffset',\n 'indexFromElement',\n ],\n // Zero-arg methods exposed as computed signals\n ['getTotalSize', 'getVirtualItems'],\n // The rest is passed as is, and can be accessed or called before initialization\n ) as unknown as AngularVirtualizer<TScrollElement, TItemElement>\n })\n}\n\nexport function injectVirtualizer<\n TScrollElement extends Element,\n TItemElement extends Element,\n>(\n options: () => PartialKeys<\n Omit<\n AngularVirtualizerOptions<TScrollElement, TItemElement>,\n 'getScrollElement'\n >,\n 'observeElementRect' | 'observeElementOffset' | 'scrollToFn'\n > & {\n scrollElement: ElementRef<TScrollElement> | TScrollElement | undefined\n },\n): AngularVirtualizer<TScrollElement, TItemElement> {\n return injectVirtualizerBase<TScrollElement, TItemElement>(() => {\n const _options = options()\n return {\n observeElementRect: observeElementRect,\n observeElementOffset: observeElementOffset,\n scrollToFn: elementScroll,\n getScrollElement: () => {\n const elementOrRef = _options.scrollElement\n return (\n (isElementRef(elementOrRef)\n ? elementOrRef.nativeElement\n : elementOrRef) ?? null\n )\n },\n ..._options,\n }\n })\n}\n\nfunction isElementRef<T extends Element>(\n elementOrRef: ElementRef<T> | T | undefined,\n): elementOrRef is ElementRef<T> {\n return elementOrRef != null && 'nativeElement' in elementOrRef\n}\n\nexport function injectWindowVirtualizer<TItemElement extends Element>(\n options: () => PartialKeys<\n AngularVirtualizerOptions<Window, TItemElement>,\n | 'getScrollElement'\n | 'observeElementRect'\n | 'observeElementOffset'\n | 'scrollToFn'\n >,\n): AngularVirtualizer<Window, TItemElement> {\n return injectVirtualizerBase<Window, TItemElement>(() => ({\n getScrollElement: () => (typeof document !== 'undefined' ? window : null),\n observeElementRect: observeWindowRect,\n observeElementOffset: observeWindowOffset,\n scrollToFn: windowScroll,\n initialOffset: () => (typeof document !== 'undefined' ? window.scrollY : 0),\n ...options(),\n }))\n}\n"],"names":["inject","ApplicationRef","DestroyRef","assertInInjectionContext","Injector","runInInjectionContext","computed","Virtualizer","untracked","linkedSignal","afterRenderEffect","signalProxy","observeElementRect","observeElementOffset","elementScroll","observeWindowRect","observeWindowOffset","windowScroll"],"mappings":"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"}