Repository: TanStack/virtual
Branch: main
Commit: c9397853af6c
Files: 515
Total size: 550.6 KB
Directory structure:
gitextract_lr2rhb18/
├── .changeset/
│ └── config.json
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ └── config.yml
│ ├── pull_request_template.md
│ ├── renovate.json
│ └── workflows/
│ ├── autofix.yml
│ ├── pr.yml
│ └── release.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│ ├── api/
│ │ ├── virtual-item.md
│ │ └── virtualizer.md
│ ├── config.json
│ ├── framework/
│ │ ├── angular/
│ │ │ └── angular-virtual.md
│ │ ├── lit/
│ │ │ └── lit-virtual.md
│ │ ├── react/
│ │ │ └── react-virtual.md
│ │ ├── solid/
│ │ │ └── solid-virtual.md
│ │ ├── svelte/
│ │ │ └── svelte-virtual.md
│ │ └── vue/
│ │ └── vue-virtual.md
│ ├── installation.md
│ └── introduction.md
├── eslint.config.js
├── examples/
│ ├── angular/
│ │ ├── dynamic/
│ │ │ ├── .devcontainer/
│ │ │ │ └── devcontainer.json
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── angular.json
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── app/
│ │ │ │ │ ├── app.component.ts
│ │ │ │ │ ├── app.config.ts
│ │ │ │ │ ├── app.routes.ts
│ │ │ │ │ ├── column-virtualizer-dynamic.component.ts
│ │ │ │ │ ├── grid-virtualizer-dynamic.component.ts
│ │ │ │ │ ├── row-virtualizer-dynamic-window.component.ts
│ │ │ │ │ ├── row-virtualizer-dynamic.component.ts
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ └── styles.css
│ │ │ ├── tsconfig.app.json
│ │ │ └── tsconfig.json
│ │ ├── fixed/
│ │ │ ├── .devcontainer/
│ │ │ │ └── devcontainer.json
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── angular.json
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── app/
│ │ │ │ │ ├── app.component.ts
│ │ │ │ │ ├── column-virtualizer-fixed.component.ts
│ │ │ │ │ ├── grid-virtualizer-fixed.component.ts
│ │ │ │ │ └── row-virtualizer-fixed.component.ts
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ └── styles.css
│ │ │ ├── tsconfig.app.json
│ │ │ └── tsconfig.json
│ │ ├── infinite-scroll/
│ │ │ ├── .devcontainer/
│ │ │ │ └── devcontainer.json
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── angular.json
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── app/
│ │ │ │ │ └── app.component.ts
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ └── styles.css
│ │ │ ├── tsconfig.app.json
│ │ │ └── tsconfig.json
│ │ ├── padding/
│ │ │ ├── .devcontainer/
│ │ │ │ └── devcontainer.json
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── angular.json
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── app/
│ │ │ │ │ ├── app.component.ts
│ │ │ │ │ ├── column-virtualizer-padding.component.ts
│ │ │ │ │ ├── grid-virtualizer-padding.component.ts
│ │ │ │ │ └── row-virtualizer-padding.component.ts
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ └── styles.css
│ │ │ ├── tsconfig.app.json
│ │ │ └── tsconfig.json
│ │ ├── smooth-scroll/
│ │ │ ├── .devcontainer/
│ │ │ │ └── devcontainer.json
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── angular.json
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── app/
│ │ │ │ │ └── app.component.ts
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ └── styles.css
│ │ │ ├── tsconfig.app.json
│ │ │ └── tsconfig.json
│ │ ├── sticky/
│ │ │ ├── .devcontainer/
│ │ │ │ └── devcontainer.json
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── angular.json
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── app/
│ │ │ │ │ └── app.component.ts
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ └── styles.css
│ │ │ ├── tsconfig.app.json
│ │ │ └── tsconfig.json
│ │ ├── table/
│ │ │ ├── .devcontainer/
│ │ │ │ └── devcontainer.json
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── angular.json
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── app/
│ │ │ │ │ ├── app.component.ts
│ │ │ │ │ └── make-data.ts
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ └── styles.css
│ │ │ ├── tsconfig.app.json
│ │ │ └── tsconfig.json
│ │ ├── variable/
│ │ │ ├── .devcontainer/
│ │ │ │ └── devcontainer.json
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── angular.json
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── app/
│ │ │ │ │ ├── app.component.ts
│ │ │ │ │ ├── column-virtualizer-variable.component.ts
│ │ │ │ │ ├── grid-virtualizer-variable.component.ts
│ │ │ │ │ └── row-virtualizer-variable.component.ts
│ │ │ │ ├── index.html
│ │ │ │ ├── main.ts
│ │ │ │ └── styles.css
│ │ │ ├── tsconfig.app.json
│ │ │ └── tsconfig.json
│ │ └── window/
│ │ ├── .devcontainer/
│ │ │ └── devcontainer.json
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── angular.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── app/
│ │ │ │ └── app.component.ts
│ │ │ ├── index.html
│ │ │ ├── main.ts
│ │ │ └── styles.css
│ │ ├── tsconfig.app.json
│ │ └── tsconfig.json
│ ├── lit/
│ │ ├── dynamic/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.css
│ │ │ │ └── main.ts
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.js
│ │ └── fixed/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.css
│ │ │ └── main.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.js
│ ├── react/
│ │ ├── dynamic/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.css
│ │ │ │ └── main.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.js
│ │ ├── fixed/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.css
│ │ │ │ └── main.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.js
│ │ ├── infinite-scroll/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.css
│ │ │ │ └── main.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.js
│ │ ├── padding/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.css
│ │ │ │ └── main.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.js
│ │ ├── scroll-padding/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.css
│ │ │ │ └── main.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.js
│ │ ├── smooth-scroll/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.css
│ │ │ │ └── main.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.js
│ │ ├── sticky/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.css
│ │ │ │ └── main.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.js
│ │ ├── table/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.css
│ │ │ │ ├── main.tsx
│ │ │ │ └── makeData.ts
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.js
│ │ ├── variable/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.css
│ │ │ │ └── main.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.js
│ │ └── window/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.css
│ │ │ └── main.tsx
│ │ ├── tsconfig.json
│ │ └── vite.config.js
│ ├── svelte/
│ │ ├── dynamic/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── App.svelte
│ │ │ │ ├── ColumnVirtualizerDynamic.svelte
│ │ │ │ ├── GridVirtualizerDynamic.svelte
│ │ │ │ ├── RowVirtualizerDynamic.svelte
│ │ │ │ ├── RowVirtualizerDynamicWindow.svelte
│ │ │ │ ├── app.css
│ │ │ │ ├── main.ts
│ │ │ │ └── vite-env.d.ts
│ │ │ ├── svelte.config.js
│ │ │ ├── tsconfig.json
│ │ │ ├── tsconfig.node.json
│ │ │ └── vite.config.ts
│ │ ├── fixed/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── App.svelte
│ │ │ │ ├── ColumnVirtualizerFixed.svelte
│ │ │ │ ├── GridVirtualizerFixed.svelte
│ │ │ │ ├── RowVirtualizerFixed.svelte
│ │ │ │ ├── app.css
│ │ │ │ ├── main.ts
│ │ │ │ └── vite-env.d.ts
│ │ │ ├── svelte.config.js
│ │ │ ├── tsconfig.json
│ │ │ ├── tsconfig.node.json
│ │ │ └── vite.config.ts
│ │ ├── infinite-scroll/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── App.svelte
│ │ │ │ ├── InfiniteRows.svelte
│ │ │ │ ├── app.css
│ │ │ │ ├── main.ts
│ │ │ │ └── vite-env.d.ts
│ │ │ ├── svelte.config.js
│ │ │ ├── tsconfig.json
│ │ │ ├── tsconfig.node.json
│ │ │ └── vite.config.ts
│ │ ├── smooth-scroll/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── App.svelte
│ │ │ │ ├── app.css
│ │ │ │ ├── main.ts
│ │ │ │ └── vite-env.d.ts
│ │ │ ├── svelte.config.js
│ │ │ ├── tsconfig.json
│ │ │ ├── tsconfig.node.json
│ │ │ └── vite.config.ts
│ │ ├── sticky/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── App.svelte
│ │ │ │ ├── app.css
│ │ │ │ ├── main.ts
│ │ │ │ └── vite-env.d.ts
│ │ │ ├── svelte.config.js
│ │ │ ├── tsconfig.json
│ │ │ ├── tsconfig.node.json
│ │ │ └── vite.config.ts
│ │ └── table/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.svelte
│ │ │ ├── app.css
│ │ │ ├── main.ts
│ │ │ ├── makeData.ts
│ │ │ └── vite-env.d.ts
│ │ ├── svelte.config.js
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ └── vue/
│ ├── dynamic/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ ├── components/
│ │ │ │ ├── ColumnVirtualizerDynamic.vue
│ │ │ │ ├── GridVirtualizerDynamic.vue
│ │ │ │ ├── RowVirtualizerDynamic.vue
│ │ │ │ ├── RowVirtualizerDynamicWindow.vue
│ │ │ │ └── utils.ts
│ │ │ ├── main.ts
│ │ │ ├── style.css
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ ├── fixed/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ ├── components/
│ │ │ │ ├── ColumnVirtualizerFixed.vue
│ │ │ │ ├── GridVirtualizerFixed.vue
│ │ │ │ └── RowVirtualizerFixed.vue
│ │ │ ├── main.ts
│ │ │ ├── style.css
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ ├── infinite-scroll/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ ├── main.ts
│ │ │ ├── style.css
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ ├── padding/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ ├── components/
│ │ │ │ ├── ColumnVirtualizerPadding.vue
│ │ │ │ ├── GridVirtualizerPadding.vue
│ │ │ │ └── RowVirtualizerPadding.vue
│ │ │ ├── main.ts
│ │ │ ├── style.css
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ ├── scroll-padding/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ ├── main.ts
│ │ │ ├── style.css
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ ├── smooth-scroll/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ ├── main.ts
│ │ │ ├── style.css
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ ├── sticky/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ ├── main.ts
│ │ │ ├── style.css
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ ├── table/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ ├── main.ts
│ │ │ ├── makeData.ts
│ │ │ ├── style.css
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
│ └── variable/
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── src/
│ │ ├── App.vue
│ │ ├── components/
│ │ │ ├── ColumnVirtualizerVariable.vue
│ │ │ ├── GridVirtualizerVariable.vue
│ │ │ ├── MasonryHorizontalVirtualizerVariable.vue
│ │ │ ├── MasonryVerticalVirtualizerVariable.vue
│ │ │ └── RowVirtualizerVariable.vue
│ │ ├── main.ts
│ │ ├── style.css
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── knip.json
├── media/
│ └── logo.sketch
├── nx.json
├── package.json
├── packages/
│ ├── angular-virtual/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── eslint.config.js
│ │ ├── ng-package.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── proxy.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── lit-virtual/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── eslint.config.js
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── tests/
│ │ │ └── index.test.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── react-virtual/
│ │ ├── CHANGELOG.md
│ │ ├── e2e/
│ │ │ └── app/
│ │ │ ├── measure-element/
│ │ │ │ ├── index.html
│ │ │ │ └── main.tsx
│ │ │ ├── scroll/
│ │ │ │ ├── index.html
│ │ │ │ └── main.tsx
│ │ │ ├── smooth-scroll/
│ │ │ │ ├── index.html
│ │ │ │ └── main.tsx
│ │ │ ├── stale-index/
│ │ │ │ ├── index.html
│ │ │ │ └── main.tsx
│ │ │ ├── test/
│ │ │ │ ├── measure-element.spec.ts
│ │ │ │ ├── scroll.spec.ts
│ │ │ │ ├── smooth-scroll.spec.ts
│ │ │ │ └── stale-index.spec.ts
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── eslint.config.js
│ │ ├── package.json
│ │ ├── playwright.config.ts
│ │ ├── src/
│ │ │ └── index.tsx
│ │ ├── tests/
│ │ │ ├── index.test.tsx
│ │ │ └── test-setup.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── solid-virtual/
│ │ ├── CHANGELOG.md
│ │ ├── eslint.config.js
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.tsx
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── svelte-virtual/
│ │ ├── CHANGELOG.md
│ │ ├── eslint.config.js
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── svelte.config.js
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── virtual-core/
│ │ ├── CHANGELOG.md
│ │ ├── eslint.config.js
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ └── utils.ts
│ │ ├── tests/
│ │ │ └── index.test.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ └── vue-virtual/
│ ├── CHANGELOG.md
│ ├── eslint.config.js
│ ├── package.json
│ ├── src/
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── pnpm-workspace.yaml
├── prettier.config.js
├── scripts/
│ └── verify-links.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .changeset/config.json
================================================
{
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
"changelog": [
"@svitejs/changesets-changelog-github-compact",
{ "repo": "TanStack/virtual" }
],
"commit": false,
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"fixed": [],
"linked": [],
"ignore": [],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
}
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto eol=lf
================================================
FILE: .github/FUNDING.yml
================================================
github: tannerlinsley
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: 🐛 Bug Report
description: Report a reproducible bug or regression
body:
- type: markdown
attributes:
value: |
Thank you for reporting an issue :pray:.
This issue tracker is for reporting reproducible bugs or regression's found in [tanstack-virtual](https://github.com/tanstack/virtual)
If you have a question about how to achieve or implement something and are struggling, please post a question
inside of tanstack-virtual's [Discussions tab](https://github.com/tanstack/virtual/discussions) instead of filing an issue.
Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already:
- tanstack-virtual's [Discussions tab](https://github.com/tanstack/virtual/discussions)
- tanstack-virtual's [Open Issues](https://github.com/tanstack/virtual/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
- tanstack-virtual's [Closed Issues](https://github.com/tanstack/virtual/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed)
The more information you fill in, the better the community can help you.
- type: textarea
id: description
attributes:
label: Describe the bug
description: Provide a clear and concise description of the challenge you are running into.
validations:
required: true
- type: input
id: link
attributes:
label: Your minimal, reproducible example
description: |
Please add a link to a minimal reproduction.
Note:
- Your bug may get fixed much faster if we can run your code and it doesn't have dependencies other than React.
- To create a shareable code example for web, you can use CodeSandbox (https://codesandbox.io/s/new) or Stackblitz (https://stackblitz.com/).
- Please make sure the example is complete and runnable without prior dependencies and free of unnecessary abstractions
- Feel free to fork any of the official CodeSandbox examples to reproduce your issue: https://github.com/tanstack/virtual/tree/main/examples/
- For React Native, you can use: https://snack.expo.dev/
- For TypeScript related issues only, a TypeScript Playground link might be sufficient: https://www.typescriptlang.org/play
- Please read these tips for providing a minimal example: https://stackoverflow.com/help/mcve.
placeholder: |
e.g. Code Sandbox, Stackblitz, Expo Snack or TypeScript playground
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: Describe the steps we have to take to reproduce the behavior.
placeholder: |
1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: Provide a clear and concise description of what you expected to happen.
placeholder: |
As a user, I expected **_ behavior but i am seeing _**
validations:
required: true
- type: dropdown
attributes:
options:
- Every time
- Often
- Sometimes
- Only once
label: How often does this bug happen?
description: |
Following the repro steps above, how easily are you able to reproduce this bug?
options: - Every time - Often - Sometimes - Only once
- type: textarea
id: screenshots_or_videos
attributes:
label: Screenshots or Videos
description: |
If applicable, add screenshots or a video to help explain your problem.
For more information on the supported file image/file types and the file size limits, please refer
to the following link: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files
placeholder: |
You can drag your video or image files inside of this editor ↓
- type: textarea
id: platform
attributes:
label: Platform
description: |
Please let us know which Operting System, Browser and Browser version you were using when the issue occurred.
placeholder: |
- OS: [e.g. macOS, Windows, Linux, iOS, Android] - Browser: [e.g. Chrome, Safari, Firefox, React Native] - Version: [e.g. 91.1]
validations:
required: true
- type: input
id: library-version
attributes:
label: tanstack-virtual version
description: |
Please let us know the exact version of tanstack-virtual you were using when the issue occurred. Please don't just put in "latest", as this is subject to change.
placeholder: |
e.g. v3.30.1
validations:
required: true
- type: input
id: ts-version
attributes:
label: TypeScript version
description: |
If you are using TypeScript, please let us know the exact version of TypeScript you were using when the issue occurred.
placeholder: |
e.g. v5.2.2
- type: textarea
id: additional
attributes:
label: Additional context
description: Add any other context about the problem here.
- type: checkboxes
id: agrees-to-terms
attributes:
label: Terms & Code of Conduct
description: By submitting this issue, you agree to follow our Code of Conduct and can verify that you have followed the requirements outlined above to the best of your ability.
options:
- label: I agree to follow this project's Code of Conduct
required: true
- label: I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: 🤔 Feature Requests & Questions
url: https://github.com/TanStack/virtual/discussions
about: Please ask and answer questions here.
- name: 💬 Community Chat
url: https://discord.gg/mQd7egN
about: A dedicated discord server hosted by TanStack
- name: 🦋 TanStack Bluesky
url: https://bsky.app/profile/tanstack.com
about: Stay up to date with new releases of our libraries
================================================
FILE: .github/pull_request_template.md
================================================
## 🎯 Changes
## ✅ Checklist
- [ ] I have followed the steps in the [Contributing guide](https://github.com/TanStack/virtual/blob/main/CONTRIBUTING.md).
- [ ] I have tested this code locally with `pnpm run test:pr`.
## 🚀 Release Impact
- [ ] This change affects published code, and I have generated a [changeset](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).
- [ ] This change is docs/CI/dev-only (no release).
================================================
FILE: .github/renovate.json
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"configMigration": true,
"extends": [
"config:recommended",
"group:allNonMajor",
"schedule:weekly",
":approveMajorUpdates",
":automergeMinor",
":disablePeerDependencies",
":maintainLockFilesMonthly",
":semanticCommits",
":semanticCommitTypeAll(chore)"
],
"ignorePresets": [":ignoreModulesAndTests"],
"labels": ["dependencies"],
"rangeStrategy": "bump",
"postUpdateOptions": ["pnpmDedupe"],
"ignoreDeps": ["@types/node", "node", "typescript"]
}
================================================
FILE: .github/workflows/autofix.yml
================================================
name: autofix.ci # needed to securely identify the workflow
on:
pull_request:
push:
branches: [main, alpha, beta]
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
autofix:
name: autofix
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup Tools
uses: tanstack/config/.github/setup@main
- name: Fix formatting
run: pnpm format
- name: Apply fixes
uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
with:
commit-message: 'ci: apply automated fixes'
================================================
FILE: .github/workflows/pr.yml
================================================
name: PR
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
cancel-in-progress: true
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
permissions:
contents: read
pull-requests: write
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
- name: Setup Tools
uses: tanstack/config/.github/setup@main
- name: Get base and head commits for `nx affected`
uses: nrwl/nx-set-shas@v4.4.0
with:
main-branch-name: main
- name: Install Playwright browsers
run: pnpm exec playwright install chromium
- name: Run Checks
run: pnpm run test:pr
preview:
name: Preview
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup Tools
uses: tanstack/config/.github/setup@main
- name: Build Packages
run: pnpm run build:all
- name: Publish Previews
run: pnpx pkg-pr-new publish --pnpm --compact './packages/*' --template './examples/*/*'
provenance:
name: Provenance
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Check Provenance
uses: danielroe/provenance-action@v0.1.1
with:
fail-on-downgrade: true
version-preview:
name: Version Preview
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup Tools
uses: TanStack/config/.github/setup@main
- name: Changeset Preview
uses: TanStack/config/.github/changeset-preview@main
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
branches: [main, alpha, beta]
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
cancel-in-progress: true
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
permissions:
contents: write
id-token: write
pull-requests: write
jobs:
release:
name: Release
if: github.repository_owner == 'TanStack'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
- name: Setup Tools
uses: tanstack/config/.github/setup@main
- name: Install Playwright browsers
run: pnpm exec playwright install chromium
- name: Run Tests
run: pnpm run test:ci
- name: Run Changesets (version or publish)
id: changesets
uses: changesets/action@v1.7.0
with:
version: pnpm run changeset:version
publish: pnpm run changeset:publish
commit: 'ci: Version Packages'
title: 'ci: Version Packages'
- name: Comment on PRs about release
if: steps.changesets.outputs.published == 'true'
uses: TanStack/config/.github/comment-on-release@main
with:
published-packages: ${{ steps.changesets.outputs.publishedPackages }}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
package-lock.json
yarn.lock
# builds
build
coverage
dist
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.next
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.history
size-plugin.json
stats-hydration.json
stats.json
stats.html
.vscode/settings.json
*.log
.cache
.idea
.nx/cache
.nx/workspace-data
.pnpm-store
.tsup
.svelte-kit
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# Playwright test artifacts
test-results/
playwright-report/
*.log
================================================
FILE: .npmrc
================================================
provenance=true
================================================
FILE: .nvmrc
================================================
24.8.0
================================================
FILE: .prettierignore
================================================
**/.next
**/.nx/cache
**/.svelte-kit
**/build
**/coverage
**/dist
**/docs
**/codemods/**/__testfixtures__
pnpm-lock.yaml
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
## Questions
If you have questions about implementation details, help or support, then please use our dedicated community forum at [GitHub Discussions](https://github.com/TanStack/virtual/discussions) **PLEASE NOTE:** If you choose to instead open an issue for your question, your issue will be immediately closed and redirected to the forum.
## Reporting Issues
If you have found what you think is a bug, please [file an issue](https://github.com/TanStack/virtual/issues/new/choose). **PLEASE NOTE:** Issues that are identified as implementation questions or non-issues will be immediately closed and redirected to [GitHub Discussions](https://github.com/TanStack/virtual/discussions)
## Suggesting new features
If you are here to suggest a feature, first create an issue if it does not already exist. From there, we will discuss use-cases for the feature and then finally discuss how it could be implemented.
## Development
If you have been assigned to fix an issue or develop a new feature, please follow these steps to get started:
- Fork this repository.
- Install dependencies
```bash
pnpm install
```
- We use [pnpm](https://pnpm.io/) v9 for package management (run in case of pnpm-related issues).
```bash
corepack enable && corepack prepare
```
- We use [nvm](https://github.com/nvm-sh/nvm) to manage node versions - please make sure to use the version mentioned in `.nvmrc`
```bash
nvm use
```
- Build all packages.
```bash
pnpm build:all
```
- Run development server.
```bash
pnpm run watch
```
- Implement your changes and tests to files in the `src/` directory and corresponding test files.
- Document your changes in the appropriate doc page.
- Git stage your required changes and commit (see below commit guidelines).
- Submit PR for review.
### Editing the docs locally and previewing the changes
The documentations for all the TanStack projects are hosted on [tanstack.com](https://tanstack.com), which is a TanStack Start application (https://github.com/TanStack/tanstack.com). You need to run this app locally to preview your changes in the `TanStack/virtual` docs.
> [!NOTE]
> The website fetches the doc pages from GitHub in production, and searches for them at `../virtual/docs` in development. Your local clone of `TanStack/virtual` needs to be in the same directory as the local clone of `TanStack/tanstack.com`.
You can follow these steps to set up the docs for local development:
1. Make a new directory called `tanstack`.
```sh
mkdir tanstack
```
2. Enter that directory and clone the [`TanStack/virtual`](https://github.com/TanStack/virtual) and [`TanStack/tanstack.com`](https://github.com/TanStack/tanstack.com) repos.
```sh
cd tanstack
git clone git@github.com:TanStack/virtual.git
# We probably don't need all the branches and commit history
# from the `tanstack.com` repo, so let's just create a shallow
# clone of the latest version of the `main` branch.
# Read more about shallow clones here:
# https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/#user-content-shallow-clones
git clone git@github.com:TanStack/tanstack.com.git --depth=1 --single-branch --branch=main
```
> [!NOTE]
> Your `tanstack` directory should look like this:
>
> ```
> tanstack/
> |
> +-- virtual/ (<-- this directory cannot be called anything else!)
> |
> +-- tanstack.com/
> ```
3. Enter the `tanstack/tanstack.com` directory, install the dependencies and run the app in dev mode:
```sh
cd tanstack.com
pnpm i
# The app will run on https://localhost:3000 by default
pnpm dev
```
4. Now you can visit http://localhost:3000/virtual/latest/docs/overview in the browser and see the changes you make in `TanStack/virtual/docs` there.
> [!WARNING]
> You will need to update the `docs/config.json` file (in `TanStack/virtual`) if you add a new documentation page!
You can see the whole process in the screen capture below:
https://github.com/fulopkovacs/form/assets/43729152/9d35a3c3-8153-4e74-9cb2-af275f7a269b
### Running examples
- Make sure you've installed the dependencies in the repo's root directory.
```bash
pnpm install
```
- If you want to run the example against your local changes, run below in the repo's root directory. Otherwise, it will be run against the latest TanStack Virtual release.
```bash
pnpm run watch
```
- Run below in the selected examples' directory.
```bash
pnpm run dev
```
#### Note on standalone execution
If you want to run an example without installing dependencies for the whole repo, just follow instructions from the example's README.md file. It will be then run against the latest TanStack Virtual release.
## Online one-click setup
You can use Gitpod (An Online Open Source VS Code like IDE which is free for Open Source) for developing online. With a single click it will start a workspace and automatically:
- clone the `TanStack/virtual` repo.
- install all the dependencies in `/` and `/docs`.
- run below in the root(`/`) to Auto-build files.
```bash
npm start
```
- run below in `/docs`.
```bash
npm run dev
```
[](https://gitpod.io/#https://github.com/TanStack/virtual)
## Changesets
This repo uses [Changesets](https://github.com/changesets/changesets) to automate releases. If your PR should release a new package version (patch, minor, or major), please run run `pnpm changeset` and commit the file. If needed, changeset descriptions can be more descriptive, and will be included in the changelog. If your PR affects docs, examples, styles, etc., you probably don't need to generate a changeset.
## Pull requests
Maintainers merge pull requests by squashing all commits and editing the commit message if necessary using the GitHub user interface.
Use an appropriate commit type. Be especially careful with breaking changes.
## Releases
For each new commit added to `main`, a GitHub Workflow is triggered which runs the [Changesets Action](https://github.com/changesets/action). This generates a preview PR showing the impact of all changesets. When this PR is merged, the package will be published to NPM.
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021-present Tanner Linsley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
# TanStack Virtual
A headless, framework‑agnostic virtualization library for rendering massive lists, grids, and tables at 60FPS while giving you full control over markup and styles.
- Framework‑agnostic & headless
- Virtualizes vertical, horizontal & grid layouts with a single hook/function
- Lightweight (10–15kb) yet powerful, with dynamic & measured sizing support
- Smooth 60FPS scrolling with sticky items and window‑scrolling utilities
### Read the docs →
## Get Involved
- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/virtual/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions
## Partners
We're looking for TanStack Virtual Partners to join our mission! Partner with us to push the boundaries of TanStack Virtual and build amazing things together.
## Explore the TanStack Ecosystem
- TanStack Config – Tooling for JS/TS packages
- TanStack DB – Reactive sync client store
- TanStack DevTools – Unified devtools panel
- TanStack Form – Type‑safe form state
- TanStack Pacer – Debouncing, throttling, batching
- TanStack Query – Async state & caching
- TanStack Ranger – Range & slider primitives
- TanStack Router – Type‑safe routing, caching & URL state
- TanStack Start – Full‑stack SSR & streaming
- TanStack Store – Reactive data store
- TanStack Table – Headless datagrids
… and more at TanStack.com »
================================================
FILE: docs/api/virtual-item.md
================================================
---
title: VirtualItem
---
The `VirtualItem` object represents a single item returned by the virtualizer. It contains information you need to render the item in the coordinate space within your virtualizer's scrollElement and other helpful properties/functions.
```tsx
export interface VirtualItem {
key: string | number | bigint
index: number
start: number
end: number
size: number
}
```
The following properties and methods are available on each VirtualItem object:
### `key`
```tsx
key: string | number | bigint
```
The unique key for the item. By default this is the item index, but should be configured via the `getItemKey` Virtualizer option.
### `index`
```tsx
index: number
```
The index of the item.
### `start`
```tsx
start: number
```
The starting pixel offset for the item. This is usually mapped to a css property or transform like `top/left` or `translateX/translateY`.
### `end`
```tsx
end: number
```
The ending pixel offset for the item. This value is not necessary for most layouts, but can be helpful so we've provided it anyway.
### `size`
```tsx
size: number
```
The size of the item. This is usually mapped to a css property like `width/height`. Before an item is measured with the `VirtualItem.measureElement` method, this will be the estimated size returned from your `estimateSize` virtualizer option. After an item is measured (if you choose to measure it at all), this value will be the number returned by your `measureElement` virtualizer option (which by default is configured to measure elements with `getBoundingClientRect()`).
### `lane`
```tsx
lane: number
```
The lane index of the item. In regular lists it will always be set to `0` but becomes useful for masonry layouts (see variable examples for more details).
================================================
FILE: docs/api/virtualizer.md
================================================
---
title: Virtualizer
---
The `Virtualizer` class is the core of TanStack Virtual. Virtualizer instances are usually created for you by your framework adapter, but you do receive the virtualizer directly.
```tsx
export class Virtualizer {
constructor(options: VirtualizerOptions)
}
```
## Required Options
### `count`
```tsx
count: number
```
The total number of items to virtualize.
### `getScrollElement`
```tsx
getScrollElement: () => TScrollElement
```
A function that returns the scrollable element for the virtualizer. It may return null if the element is not available yet.
### `estimateSize`
```tsx
estimateSize: (index: number) => number
```
> 🧠 If you are dynamically measuring your elements, it's recommended to estimate the largest possible size (width/height, within comfort) of your items. This will help the virtualizer calculate more accurate initial positions.
This function is passed the index of each item and should return the actual size (or estimated size if you will be dynamically measuring items with `virtualItem.measureElement`) for each item. This measurement should return either the width or height depending on the orientation of your virtualizer.
## Optional Options
### `enabled`
```tsx
enabled?: boolean
```
Set to `false` to disable scrollElement observers and reset the virtualizer's state
### `debug`
```tsx
debug?: boolean
```
Set to `true` to enable debug logs
### `initialRect`
```tsx
initialRect?: Rect
```
The initial `Rect` of the scrollElement. This is mostly useful if you need to run the virtualizer in an SSR environment, otherwise the initialRect will be calculated on mount by the `observeElementRect` implementation.
### `onChange`
```tsx
onChange?: (instance: Virtualizer, sync: boolean) => void
```
A callback function that fires when the virtualizer's internal state changes. It's passed the virtualizer instance and the sync parameter.
The sync parameter indicates whether scrolling is currently in progress. It is `true` when scrolling is ongoing, and `false` when scrolling has stopped or other actions (such as resizing) are being performed.
### `overscan`
```tsx
overscan?: number
```
The number of items to render above and below the visible area. Increasing this number will increase the amount of time it takes to render the virtualizer, but might decrease the likelihood of seeing slow-rendering blank items at the top and bottom of the virtualizer when scrolling. The default value is `1`.
### `horizontal`
```tsx
horizontal?: boolean
```
Set this to `true` if your virtualizer is oriented horizontally.
### `paddingStart`
```tsx
paddingStart?: number
```
The padding to apply to the start of the virtualizer in pixels.
### `paddingEnd`
```tsx
paddingEnd?: number
```
The padding to apply to the end of the virtualizer in pixels.
### `scrollPaddingStart`
```tsx
scrollPaddingStart?: number
```
The padding to apply to the start of the virtualizer in pixels when scrolling to an element.
### `scrollPaddingEnd`
```tsx
scrollPaddingEnd?: number
```
The padding to apply to the end of the virtualizer in pixels when scrolling to an element.
### `initialOffset`
```tsx
initialOffset?: number | (() => number)
```
The position where the list is scrolled to on render. This is useful if you are rendering the virtualizer in a SSR environment or are conditionally rendering the virtualizer.
### `getItemKey`
```tsx
getItemKey?: (index: number) => Key
```
This function is passed the index of each item and should return a unique key for that item. The default functionality of this function is to return the index of the item, but you should override this when possible to return a unique identifier for each item across the entire set.
**Note:** The virtualizer automatically invalidates its measurement cache when measurement-affecting options change, ensuring `getTotalSize()` and other measurements return fresh values. While the virtualizer intelligently tracks which options actually affect measurements, it's still better to memoize `getItemKey` (e.g., using `useCallback` in React) to avoid unnecessary recalculations.
### `rangeExtractor`
```tsx
rangeExtractor?: (range: Range) => number[]
```
This function receives visible range indexes and should return array of indexes to render. This is useful if you need to add or remove items from the virtualizer manually regardless of the visible range, eg. rendering sticky items, headers, footers, etc. The default range extractor implementation will return the visible range indexes and is exported as `defaultRangeExtractor`.
### `scrollToFn`
```tsx
scrollToFn?: (
offset: number,
options: { adjustments?: number; behavior?: 'auto' | 'smooth' },
instance: Virtualizer,
) => void
```
An optional function that (if provided) should implement the scrolling behavior for your scrollElement. It will be called with the following arguments:
- An `offset` (in pixels) to scroll towards.
- An object indicating whether there was a difference between the estimated size and actual size (`adjustments`) and/or whether scrolling was called with a smooth animation (`behaviour`).
- The virtualizer instance itself.
Note that built-in scroll implementations are exported as `elementScroll` and `windowScroll`, which are automatically configured by the framework adapter functions like `useVirtualizer` or `useWindowVirtualizer`.
### `observeElementRect`
```tsx
observeElementRect: (
instance: Virtualizer,
cb: (rect: Rect) => void,
) => void | (() => void)
```
An optional function that if provided is called when the scrollElement changes and should implement the initial measurement and continuous monitoring of the scrollElement's `Rect` (an object with `width` and `height`). It's called with the instance (which also gives you access to the scrollElement via `instance.scrollElement`. Built-in implementations are exported as `observeElementRect` and `observeWindowRect` which are automatically configured for you by your framework adapter's exported functions like `useVirtualizer` or `useWindowVirtualizer`.
### `observeElementOffset`
```tsx
observeElementOffset: (
instance: Virtualizer,
cb: (offset: number) => void,
) => void | (() => void)
```
An optional function that if provided is called when the scrollElement changes and should implement the initial measurement and continuous monitoring of the scrollElement's scroll offset (a number). It's called with the instance (which also gives you access to the scrollElement via `instance.scrollElement`. Built-in implementations are exported as `observeElementOffset` and `observeWindowOffset` which are automatically configured for you by your framework adapter's exported functions like `useVirtualizer` or `useWindowVirtualizer`.
### `measureElement`
```tsx
measureElement?: (
element: TItemElement,
entry: ResizeObserverEntry | undefined,
instance: Virtualizer,
) => number
```
This optional function is called when the virtualizer needs to dynamically measure the size (width or height) of an item.
> 🧠 You can use `instance.options.horizontal` to determine if the width or height of the item should be measured.
### `scrollMargin`
```tsx
scrollMargin?: number
```
With this option, you can specify where the scroll offset should originate. Typically, this value represents the space between the beginning of the scrolling element and the start of the list. This is especially useful in common scenarios such as when you have a header preceding a window virtualizer or when multiple virtualizers are utilized within a single scrolling element. If you are using absolute positioning of elements, you should take into account the `scrollMargin` in your CSS transform:
```tsx
transform: `translateY(${
virtualRow.start - rowVirtualizer.options.scrollMargin
}px)`
```
To dynamically measure value for `scrollMargin` you can use `getBoundingClientRect()` or ResizeObserver. This is helpful in scenarios when items above your virtual list might change their height.
### `gap`
```tsx
gap?: number
```
This option allows you to set the spacing between items in the virtualized list. It's particularly useful for maintaining a consistent visual separation between items without having to manually adjust each item's margin or padding. The value is specified in pixels.
### `lanes`
```tsx
lanes: number
```
The number of lanes the list is divided into (aka columns for vertical lists and rows for horizontal lists).
### `isScrollingResetDelay`
```tsx
isScrollingResetDelay: number
```
This option allows you to specify the duration to wait after the last scroll event before resetting the isScrolling instance property. The default value is 150 milliseconds.
The implementation of this option is driven by the need for a reliable mechanism to handle scrolling behavior across different browsers. Until all browsers uniformly support the scrollEnd event.
### `useScrollendEvent`
```tsx
useScrollendEvent: boolean
```
Determines whether to use the native scrollend event to detect when scrolling has stopped. If set to false, a debounced fallback is used to reset the isScrolling instance property after isScrollingResetDelay milliseconds. The default value is `false`.
The implementation of this option is driven by the need for a reliable mechanism to handle scrolling behavior across different browsers. Until all browsers uniformly support the scrollEnd event.
### `isRtl`
```tsx
isRtl: boolean
```
Whether to invert horizontal scrolling to support right-to-left language locales.
### `useAnimationFrameWithResizeObserver`
```tsx
useAnimationFrameWithResizeObserver: boolean
```
**Default:** `false`
When enabled, defers ResizeObserver measurement processing to the next animation frame using `requestAnimationFrame`.
**Important:** This option typically **should not be enabled** in most cases. ResizeObserver callbacks already execute at an optimal time in the browser's rendering pipeline (after layout, before paint), and the measurements provided in the callback are pre-computed by the browser without causing additional reflows.
**Potential use cases:**
- If you're performing heavy DOM mutations in response to size changes and want to batch them with the next render cycle
- As a workaround for the "ResizeObserver loop completed with undelivered notifications" error (though this usually indicates a deeper issue that should be fixed)
**Tradeoffs:**
- **Adds ~16ms delay:** Measurements are deferred to the next frame, which can cause visual artifacts, stale measurements, or slower time-to-interactive
- **No batching benefit:** ResizeObserver already batches multiple element resizes into a single callback
- **Defeats optimization:** The browser has already computed the measurements synchronously; deferring them provides no performance benefit for reading values
Only enable this option if you have a specific reason and have measured that it improves your use case.
## Virtualizer Instance
The following properties and methods are available on the virtualizer instance:
### `options`
```tsx
options: readonly Required>
```
The current options for the virtualizer. This property is updated via your framework adapter and is read-only.
### `scrollElement`
```tsx
scrollElement: readonly TScrollElement | null
```
The current scrollElement for the virtualizer. This property is updated via your framework adapter and is read-only.
### `getVirtualItems`
```tsx
type getVirtualItems = () => VirtualItem[]
```
Returns the virtual items for the current state of the virtualizer.
### `getVirtualIndexes`
```tsx
type getVirtualIndexes = () => number[]
```
Returns the virtual row indexes for the current state of the virtualizer.
### `scrollToOffset`
```tsx
scrollToOffset: (
toOffset: number,
options?: {
align?: 'start' | 'center' | 'end' | 'auto',
behavior?: 'auto' | 'smooth'
}
) => void
```
Scrolls the virtualizer to the pixel offset provided. You can optionally pass an alignment mode to anchor the scroll to a specific part of the scrollElement.
### `scrollToIndex`
```tsx
scrollToIndex: (
index: number,
options?: {
align?: 'start' | 'center' | 'end' | 'auto',
behavior?: 'auto' | 'smooth'
}
) => void
```
Scrolls the virtualizer to the items of the index provided. You can optionally pass an alignment mode to anchor the scroll to a specific part of the scrollElement.
> 🧠 During smooth scrolling, the virtualizer only measures items within a buffer range around the scroll target. Items far from the target are skipped to prevent their size changes from shifting the target position and breaking the smooth animation.
>
> Because of this, the preferred layout strategy for smooth scrolling is **block translation** — translate the entire rendered block using the first item's `start` offset, rather than positioning each item independently with absolute positioning. This ensures items stay correctly positioned relative to each other even when some measurements are skipped.
### `scrollBy`
```tsx
scrollBy: (
delta: number,
options?: {
behavior?: 'auto' | 'smooth'
}
) => void
```
Scrolls the virtualizer by the specified number of pixels relative to the current scroll position.
### `getTotalSize`
```tsx
getTotalSize: () => number
```
Returns the total size in pixels for the virtualized items. This measurement will incrementally change if you choose to dynamically measure your elements as they are rendered.
### `measure`
```tsx
measure: () => void
```
Resets any prev item measurements.
### `measureElement`
```tsx
measureElement: (el: TItemElement | null) => void
```
Measures the element using your configured `measureElement` virtualizer option. You are responsible for calling this in your virtualizer markup when the component is rendered (eg. using something like React's ref callback prop) also adding `data-index`
```tsx
...
```
By default the `measureElement` virtualizer option is configured to measure elements with `getBoundingClientRect()`.
### `resizeItem`
```tsx
resizeItem: (index: number, size: number) => void
```
Change the virtualized item's size manually. Use this function to manually set the size calculated for this index. Useful in occations when using some custom morphing transition and you know the morphed item's size beforehand.
You can also use this method with a throttled ResizeObserver instead of `Virtualizer.measureElement` to reduce re-rendering.
> ⚠️ Please be aware that manually changing the size of an item when using `Virtualizer.measureElement` to monitor that item, will result in unpredictable behaviour as the `Virtualizer.measureElement` is also changing the size. However you can use one of resizeItem or measureElement in the same virtualizer instance but on different item indexes.
### `scrollRect`
```tsx
scrollRect: Rect
```
Current `Rect` of the scroll element.
### `shouldAdjustScrollPositionOnItemSizeChange`
```tsx
shouldAdjustScrollPositionOnItemSizeChange: undefined | ((item: VirtualItem, delta: number, instance: Virtualizer) => boolean)
```
The shouldAdjustScrollPositionOnItemSizeChange method enables fine-grained control over the adjustment of scroll position when the size of dynamically rendered items differs from the estimated size. When jumping in the middle of the list and scrolling backward new elements may have a different size than the initially estimated size. This discrepancy can cause subsequent items to shift, potentially disrupting the user's scrolling experience, particularly when navigating backward through the list.
### `isScrolling`
```tsx
isScrolling: boolean
```
Boolean flag indicating if list is currently being scrolled.
### `scrollDirection`
```tsx
scrollDirection: 'forward' | 'backward' | null
```
This option indicates the direction of scrolling, with possible values being 'forward' for scrolling downwards and 'backward' for scrolling upwards. The value is set to null when there is no active scrolling.
### `scrollOffset`
```tsx
scrollOffset: number
```
This option represents the current scroll position along the scrolling axis. It is measured in pixels from the starting point of the scrollable area.
================================================
FILE: docs/config.json
================================================
{
"$schema": "https://raw.githubusercontent.com/TanStack/tanstack.com/main/tanstack-docs-config.schema.json",
"docSearch": {
"appId": "",
"indexName": "",
"apiKey": ""
},
"sections": [
{
"label": "Getting Started",
"children": [
{ "label": "Introduction", "to": "introduction" },
{ "label": "Installation", "to": "installation" }
],
"frameworks": [
{
"label": "react",
"children": [
{
"label": "React Virtual",
"to": "framework/react/react-virtual"
}
]
},
{
"label": "angular",
"children": [
{
"label": "Angular Virtual",
"to": "framework/angular/angular-virtual"
}
]
},
{
"label": "solid",
"children": [
{ "label": "Solid Virtual", "to": "framework/solid/solid-virtual" }
]
},
{
"label": "svelte",
"children": [
{
"label": "Svelte Virtual",
"to": "framework/svelte/svelte-virtual"
}
]
},
{
"label": "vue",
"children": [
{
"label": "Vue Virtual",
"to": "framework/vue/vue-virtual"
}
]
}
]
},
{
"label": "Core APIs",
"children": [
{ "label": "Virtualizer", "to": "api/virtualizer" },
{ "label": "VirtualItem", "to": "api/virtual-item" }
]
},
{
"label": "Examples",
"children": [],
"frameworks": [
{
"label": "angular",
"children": [
{
"to": "framework/angular/examples/fixed",
"label": "Fixed"
},
{
"to": "framework/angular/examples/variable",
"label": "Variable"
},
{
"to": "framework/angular/examples/dynamic",
"label": "Dynamic"
},
{
"to": "framework/angular/examples/padding",
"label": "Padding"
},
{
"to": "framework/angular/examples/sticky",
"label": "Sticky"
},
{
"to": "framework/angular/examples/infinite-scroll",
"label": "Infinite Scroll"
},
{
"to": "framework/angular/examples/smooth-scroll",
"label": "Smooth Scroll"
},
{
"to": "framework/angular/examples/table",
"label": "Table"
},
{
"to": "framework/angular/examples/window",
"label": "Window"
}
]
},
{
"label": "react",
"children": [
{
"to": "framework/react/examples/fixed",
"label": "Fixed"
},
{
"to": "framework/react/examples/variable",
"label": "Variable"
},
{
"to": "framework/react/examples/dynamic",
"label": "Dynamic"
},
{
"to": "framework/react/examples/padding",
"label": "Padding"
},
{
"to": "framework/react/examples/sticky",
"label": "Sticky"
},
{
"to": "framework/react/examples/infinite-scroll",
"label": "Infinite Scroll"
},
{
"to": "framework/react/examples/smooth-scroll",
"label": "Smooth Scroll"
},
{
"to": "framework/react/examples/table",
"label": "Table"
},
{
"to": "framework/react/examples/window",
"label": "Window"
}
]
},
{
"label": "svelte",
"children": [
{
"to": "framework/svelte/examples/fixed",
"label": "Fixed"
},
{
"to": "framework/svelte/examples/variable",
"label": "Variable"
},
{
"to": "framework/svelte/examples/dynamic",
"label": "Dynamic"
},
{
"to": "framework/svelte/examples/sticky",
"label": "Sticky"
},
{
"to": "framework/svelte/examples/infinite-scroll",
"label": "Infinite Scroll"
},
{
"to": "framework/svelte/examples/smooth-scroll",
"label": "Smooth Scroll"
},
{
"to": "framework/svelte/examples/table",
"label": "Table"
}
]
},
{
"label": "vue",
"children": [
{
"to": "framework/vue/examples/fixed",
"label": "Fixed"
},
{
"to": "framework/vue/examples/variable",
"label": "Variable"
},
{
"to": "framework/vue/examples/dynamic",
"label": "Dynamic"
},
{
"to": "framework/vue/examples/sticky",
"label": "Sticky"
},
{
"to": "framework/vue/examples/infinite-scroll",
"label": "Infinite Scroll"
},
{
"to": "framework/vue/examples/smooth-scroll",
"label": "Smooth Scroll"
},
{
"to": "framework/vue/examples/table",
"label": "Table"
},
{
"to": "framework/vue/examples/padding",
"label": "Padding"
},
{
"to": "framework/vue/examples/scroll-padding",
"label": "Scroll Padding"
}
]
},
{
"label": "lit",
"children": [
{
"to": "framework/lit/examples/fixed",
"label": "Fixed"
},
{
"to": "framework/lit/examples/dynamic",
"label": "Dynamic"
}
]
}
]
}
]
}
================================================
FILE: docs/framework/angular/angular-virtual.md
================================================
---
title: Angular Virtual
---
The `@tanstack/angular-virtual` adapter is a wrapper around the core virtual logic.
## `injectVirtualizer`
```ts
function injectVirtualizer(
options: PartialKeys<
Omit, 'getScrollElement'>,
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
> & { scrollElement: ElementRef | TScrollElement | undefined },
): AngularVirtualizer
```
This function returns an `AngularVirtualizer` instance configured to work with an HTML element as the scrollElement.
## `injectWindowVirtualizer`
```ts
function injectWindowVirtualizer(
options: PartialKeys<
VirtualizerOptions,
| 'getScrollElement'
| 'observeElementRect'
| 'observeElementOffset'
| 'scrollToFn'
>,
): AngularVirtualizer
```
This function returns a window-based `AngularVirtualizer` instance configured to work with the window as the scrollElement.
================================================
FILE: docs/framework/lit/lit-virtual.md
================================================
---
title: Lit Virtual
---
The `@tanstack/lit-virtual` adapter is a wrapper around the core virtual logic.
## `createVirtualizer`
```tsx
private virtualizerController = new VirtualizerController(
options: PartialKeys< VirtualizerOptions,
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
)
```
This class stands for a standard `Virtualizer` instance configured to work with an HTML element as the scrollElement.
This will create a Lit Controller which can be accessed in the element render method.
```tsx
render() {
const virtualizer = this.virtualizerController.getVirtualizer();
const virtualItems = virtualizer.getVirtualItems();
}
)
```
## `createWindowVirtualizer`
```tsx
private windowVirtualizerController = new WindowVirtualizerController(
options: PartialKeys< VirtualizerOptions,
'getScrollElement' | 'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
```
This class stands of window-based `Virtualizer` instance configured to work with an HTML element as the scrollElement.
================================================
FILE: docs/framework/react/react-virtual.md
================================================
---
title: React Virtual
---
The `@tanstack/react-virtual` adapter is a wrapper around the core virtual logic.
## `useVirtualizer`
```tsx
function useVirtualizer(
options: PartialKeys<
ReactVirtualizerOptions,
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
>,
): Virtualizer
```
This function returns a standard `Virtualizer` instance configured to work with an HTML element as the scrollElement.
## `useWindowVirtualizer`
```tsx
function useWindowVirtualizer(
options: PartialKeys<
ReactVirtualizerOptions,
| 'getScrollElement'
| 'observeElementRect'
| 'observeElementOffset'
| 'scrollToFn'
>,
): Virtualizer
```
This function returns a window-based `Virtualizer` instance configured to work with the window as the scrollElement.
## React-Specific Options
### `useFlushSync`
```tsx
type ReactVirtualizerOptions =
VirtualizerOptions & {
useFlushSync?: boolean
}
```
Both `useVirtualizer` and `useWindowVirtualizer` accept a `useFlushSync` option that controls whether React's `flushSync` is used for synchronous updates.
- **Type**: `boolean`
- **Default**: `true`
- **Description**: When `true`, the virtualizer will use `flushSync` from `react-dom` to ensure synchronous rendering during scroll events. This provides the most accurate scrolling behavior but may impact performance in some scenarios.
#### When to disable `useFlushSync`
You may want to set `useFlushSync: false` in the following scenarios:
- **React 19 compatibility**: In React 19, you may see the following console warning when scrolling:
```
flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task.
```
Setting `useFlushSync: false` will eliminate this warning by allowing React to batch updates naturally.
- **Performance optimization**: If you experience performance issues with rapid scrolling on lower-end devices
- **Testing environments**: When running tests that don't require synchronous DOM updates
- **Non-critical lists**: When slight visual delays during scrolling are acceptable for better overall performance
#### Example
```tsx
const virtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
useFlushSync: false, // Disable synchronous updates
})
```
================================================
FILE: docs/framework/solid/solid-virtual.md
================================================
---
title: Solid Virtual
---
The `@tanstack/solid-virtual` adapter is a wrapper around the core virtual logic.
## `createVirtualizer`
```tsx
function createVirtualizer(
options: PartialKeys<
VirtualizerOptions,
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
>,
): Virtualizer
```
This function returns a standard `Virtualizer` instance configured to work with an HTML element as the scrollElement.
## `createWindowVirtualizer`
```tsx
function createWindowVirtualizer(
options: PartialKeys<
VirtualizerOptions,
| 'getScrollElement'
| 'observeElementRect'
| 'observeElementOffset'
| 'scrollToFn'
>,
): Virtualizer
```
This function returns a window-based `Virtualizer` instance configured to work with the window as the scrollElement.
================================================
FILE: docs/framework/svelte/svelte-virtual.md
================================================
---
title: Svelte Virtual
---
The `@tanstack/svelte-virtual` adapter is a wrapper around the core virtual logic.
## `createVirtualizer`
```tsx
function createVirtualizer(
options: PartialKeys<
VirtualizerOptions,
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
>,
): Virtualizer
```
This function returns a standard `Virtualizer` instance configured to work with an HTML element as the scrollElement.
## `createWindowVirtualizer`
```tsx
function createWindowVirtualizer(
options: PartialKeys<
VirtualizerOptions,
| 'getScrollElement'
| 'observeElementRect'
| 'observeElementOffset'
| 'scrollToFn'
>,
): Virtualizer
```
This function returns a window-based `Virtualizer` instance configured to work with the window as the scrollElement.
================================================
FILE: docs/framework/vue/vue-virtual.md
================================================
---
title: Vue Virtual
---
The `@tanstack/vue-virtual` adapter is a wrapper around the core virtual logic.
## `useVirtualizer`
```tsx
function useVirtualizer(
options: PartialKeys<
VirtualizerOptions,
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
>,
): Virtualizer
```
This function returns a standard `Virtualizer` instance configured to work with an HTML element as the scrollElement.
## `useWindowVirtualizer`
```tsx
function useWindowVirtualizer(
options: PartialKeys<
VirtualizerOptions,
| 'getScrollElement'
| 'observeElementRect'
| 'observeElementOffset'
| 'scrollToFn'
>,
): Virtualizer
```
This function returns a window-based `Virtualizer` instance configured to work with the window as the scrollElement.
================================================
FILE: docs/installation.md
================================================
---
title: Installation
---
Before we dig in to the API, let's get you set up!
Install your TanStack Virtual adapter as a dependency using your favorite npm package manager
## React Virtual
```bash
npm install @tanstack/react-virtual
```
## Solid Virtual
```bash
npm install @tanstack/solid-virtual
```
## Svelte Virtual
```bash
npm install @tanstack/svelte-virtual
```
## Vue Virtual
```bash
npm install @tanstack/vue-virtual
```
## Lit Virtual
```bash
npm install @tanstack/lit-virtual
```
## Angular Virtual
```bash
npm install @tanstack/angular-virtual
```
## Virtual Core (no framework)
```bash
npm install @tanstack/virtual-core
```
================================================
FILE: docs/introduction.md
================================================
---
title: Introduction
---
TanStack Virtual is a headless UI utility for virtualizing long lists of elements in JS/TS, React, Vue, Svelte, Solid, Lit, and Angular. It is not a component therefore does not ship with or render any markup or styles for you. While this requires a bit of markup and styles from you, you will retain 100% control over your styles, design and implementation.
## The Virtualizer
At the heart of TanStack Virtual is the `Virtualizer`. Virtualizers can be oriented on either the vertical (default) or horizontal axes which makes it possible to achieve vertical, horizontal and even grid-like virtualization by combining the two axis configurations together.
Here is just a quick example of what it looks like to virtualize a long list within a div using TanStack Virtual in React:
```tsx
import { useVirtualizer } from '@tanstack/react-virtual';
function App() {
// The scrollable element for your list
const parentRef = React.useRef(null)
// The virtualizer
const rowVirtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 35,
})
return (
<>
{/* The scrollable element for your list */}
{/* The large inner element to hold all of the items */}
{/* Only the visible items in the virtualizer, manually positioned to be in view */}
{rowVirtualizer.getVirtualItems().map((virtualItem) => (
Row {virtualItem.index}
))}
>
)
}
```
Let's dig into some more examples!
================================================
FILE: eslint.config.js
================================================
// @ts-check
import { tanstackConfig } from '@tanstack/eslint-config'
export default [
...tanstackConfig,
{
name: 'tanstack/temp',
rules: {
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-unnecessary-condition': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
'no-self-assign': 'off',
},
},
]
================================================
FILE: examples/angular/dynamic/.devcontainer/devcontainer.json
================================================
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:18"
}
================================================
FILE: examples/angular/dynamic/.gitignore
================================================
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
================================================
FILE: examples/angular/dynamic/README.md
================================================
# @tanstack/virtualExampleAngularDynamic
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
================================================
FILE: examples/angular/dynamic/angular.json
================================================
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "pnpm",
"analytics": false,
"cache": {
"enabled": false
}
},
"newProjectRoot": "projects",
"projects": {
"@tanstack/virtual-example-angular-dynamic": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/tanstack/virtual-example-angular-dynamic",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico"],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "@tanstack/virtual-example-angular-dynamic:build:production"
},
"development": {
"buildTarget": "@tanstack/virtual-example-angular-dynamic:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "@tanstack/virtual-example-angular-dynamic:build"
}
}
}
}
}
}
================================================
FILE: examples/angular/dynamic/package.json
================================================
{
"name": "@tanstack/virtual-example-angular-dynamic",
"private": true,
"type": "module",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"dependencies": {
"@angular/animations": "^18.1.0",
"@angular/common": "^18.1.0",
"@angular/compiler": "^18.1.0",
"@angular/core": "^18.1.0",
"@angular/forms": "^18.1.0",
"@angular/platform-browser": "^18.1.0",
"@angular/platform-browser-dynamic": "^18.1.0",
"@angular/router": "^18.1.0",
"@faker-js/faker": "^8.4.1",
"@tanstack/angular-virtual": "^4.0.11",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.1.0",
"@angular/cli": "^18.1.0",
"@angular/compiler-cli": "^18.1.0",
"typescript": "5.4.5"
}
}
================================================
FILE: examples/angular/dynamic/src/app/app.component.ts
================================================
import { Component } from '@angular/core'
import { RouterLink, RouterOutlet } from '@angular/router'
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterLink, RouterOutlet],
template: `
These components are using dynamic sizes. This means that
each element's exact dimensions are unknown when rendered. An estimated
dimension is used to get an a initial measurement, then this measurement
is readjusted on the fly as each element is rendered.
@for (row of virtualizer.getVirtualItems(); track row.index) {
Row {{ row.index }}
}
`,
styles: `
.scroll-container {
height: 200px;
width: 400px;
overflow: auto;
}
`,
})
export class RowVirtualizerFixed {
scrollElement = viewChild>('scrollElement')
virtualizer = injectVirtualizer(() => ({
scrollElement: this.scrollElement(),
count: 10000,
estimateSize: () => 35,
overscan: 5,
}))
}
================================================
FILE: examples/angular/fixed/src/index.html
================================================
@tanstack/virtualExampleAngularFixed
================================================
FILE: examples/angular/fixed/src/main.ts
================================================
import { bootstrapApplication } from '@angular/platform-browser'
import { AppComponent } from './app/app.component'
bootstrapApplication(AppComponent).catch((err) => console.error(err))
================================================
FILE: examples/angular/fixed/src/styles.css
================================================
*,
*:before,
*:after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
.list-item-odd {
background-color: #fff;
}
================================================
FILE: examples/angular/fixed/tsconfig.app.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"]
}
================================================
FILE: examples/angular/fixed/tsconfig.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": ["ES2022", "dom"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
================================================
FILE: examples/angular/infinite-scroll/.devcontainer/devcontainer.json
================================================
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:18"
}
================================================
FILE: examples/angular/infinite-scroll/.gitignore
================================================
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
================================================
FILE: examples/angular/infinite-scroll/README.md
================================================
# @tanstack/virtualExampleAngularInfiniteScroll
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
================================================
FILE: examples/angular/infinite-scroll/angular.json
================================================
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"cli": {
"packageManager": "pnpm",
"analytics": false,
"cache": {
"enabled": false
}
},
"projects": {
"@tanstack/virtual-example-angular-infinite-scroll": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/tanstack/virtual-example-angular-infinite-scroll",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico"],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "@tanstack/virtual-example-angular-infinite-scroll:build:production"
},
"development": {
"buildTarget": "@tanstack/virtual-example-angular-infinite-scroll:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "@tanstack/virtual-example-angular-infinite-scroll:build"
}
}
}
}
}
}
================================================
FILE: examples/angular/infinite-scroll/package.json
================================================
{
"name": "@tanstack/virtual-example-angular-infinite-scroll",
"private": true,
"type": "module",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"dependencies": {
"@angular/animations": "^18.1.0",
"@angular/common": "^18.1.0",
"@angular/compiler": "^18.1.0",
"@angular/core": "^18.1.0",
"@angular/forms": "^18.1.0",
"@angular/platform-browser": "^18.1.0",
"@angular/platform-browser-dynamic": "^18.1.0",
"@angular/router": "^18.1.0",
"@tanstack/angular-query-experimental": "5.80.7",
"@tanstack/angular-virtual": "^4.0.11",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.1.0",
"@angular/cli": "^18.1.0",
"@angular/compiler-cli": "^18.1.0",
"typescript": "5.4.5"
}
}
================================================
FILE: examples/angular/infinite-scroll/src/app/app.component.ts
================================================
import {
ChangeDetectionStrategy,
Component,
ElementRef,
computed,
effect,
viewChild,
} from '@angular/core'
import { injectVirtualizer } from '@tanstack/angular-virtual'
import {
QueryClient,
injectInfiniteQuery,
provideQueryClient,
} from '@tanstack/angular-query-experimental'
async function fetchServerPage(
limit: number,
offset: number = 0,
): Promise<{ rows: string[]; nextOffset: number }> {
const rows = new Array(limit)
.fill(0)
.map((e, i) => `Async loaded row #${i + offset * limit}`)
await new Promise((r) => setTimeout(r, 500))
return { rows, nextOffset: offset + 1 }
}
@Component({
selector: 'infinite-scroll',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
This infinite scroll example uses Angular Query's injectInfiniteScroll
function to fetch infinite data from a posts endpoint and then a
rowVirtualizer is used along with a loader-row placed at the bottom of the
list to trigger the next page to load.
}
`,
styles: `
.scroll-container {
height: 500px;
width: 100%;
overflow: auto;
}
`,
providers: [provideQueryClient(new QueryClient())],
})
export class InfiniteScrollComponent {
query = injectInfiniteQuery(() => ({
queryKey: ['rows'],
queryFn: ({ pageParam }) => fetchServerPage(10, pageParam),
initialPageParam: 0,
getNextPageParam: (_lastGroup, groups) => groups.length,
}))
allRows = computed(
() => this.query.data()?.pages.flatMap((d) => d.rows) ?? [],
)
scrollElement = viewChild>('scrollElement')
virtualizer = injectVirtualizer(() => ({
scrollElement: this.scrollElement(),
count: this.query.hasNextPage()
? this.allRows().length + 1
: this.allRows().length,
estimateSize: () => 100,
overscan: 5,
}))
#fetchNextPage = effect(
() => {
const lastItem =
this.virtualizer.getVirtualItems()[
this.virtualizer.getVirtualItems().length - 1
]
if (!lastItem) {
return
}
if (
lastItem.index >= this.allRows().length - 1 &&
this.query.hasNextPage() &&
!this.query.isFetchingNextPage()
) {
this.query.fetchNextPage()
}
},
{ allowSignalWrites: true },
)
}
@Component({
selector: 'app-root',
standalone: true,
imports: [InfiniteScrollComponent],
template: '',
})
export class AppComponent {}
================================================
FILE: examples/angular/infinite-scroll/src/index.html
================================================
@tanstack/virtualExampleAngularInfiniteScroll
================================================
FILE: examples/angular/infinite-scroll/src/main.ts
================================================
import { bootstrapApplication } from '@angular/platform-browser'
import { AppComponent } from './app/app.component'
bootstrapApplication(AppComponent).catch((err) => console.error(err))
================================================
FILE: examples/angular/infinite-scroll/src/styles.css
================================================
*,
*:before,
*:after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
.list-item-odd {
background-color: #fff;
}
button {
border: 1px solid gray;
}
================================================
FILE: examples/angular/infinite-scroll/tsconfig.app.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"]
}
================================================
FILE: examples/angular/infinite-scroll/tsconfig.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": ["ES2022", "dom"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
================================================
FILE: examples/angular/padding/.devcontainer/devcontainer.json
================================================
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:18"
}
================================================
FILE: examples/angular/padding/.gitignore
================================================
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
================================================
FILE: examples/angular/padding/README.md
================================================
# @tanstack/virtualExampleAngularPadding
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
================================================
FILE: examples/angular/padding/angular.json
================================================
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"cli": {
"packageManager": "pnpm",
"analytics": false,
"cache": {
"enabled": false
}
},
"projects": {
"@tanstack/virtual-example-angular-padding": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/tanstack/virtual-example-angular-padding",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico"],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "@tanstack/virtual-example-angular-padding:build:production"
},
"development": {
"buildTarget": "@tanstack/virtual-example-angular-padding:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "@tanstack/virtual-example-angular-padding:build"
}
}
}
}
}
}
================================================
FILE: examples/angular/padding/package.json
================================================
{
"name": "@tanstack/virtual-example-angular-padding",
"private": true,
"type": "module",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"dependencies": {
"@angular/animations": "^18.1.0",
"@angular/common": "^18.1.0",
"@angular/compiler": "^18.1.0",
"@angular/core": "^18.1.0",
"@angular/forms": "^18.1.0",
"@angular/platform-browser": "^18.1.0",
"@angular/platform-browser-dynamic": "^18.1.0",
"@angular/router": "^18.1.0",
"@tanstack/angular-virtual": "^4.0.11",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.1.0",
"@angular/cli": "^18.1.0",
"@angular/compiler-cli": "^18.1.0",
"typescript": "5.4.5"
}
}
================================================
FILE: examples/angular/padding/src/app/app.component.ts
================================================
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { ColumnVirtualizerPadding } from './column-virtualizer-padding.component'
import { GridVirtualizerPadding } from './grid-virtualizer-padding.component'
import { RowVirtualizerPadding } from './row-virtualizer-padding.component'
@Component({
selector: 'app-root',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ColumnVirtualizerPadding,
GridVirtualizerPadding,
RowVirtualizerPadding,
],
template: `
These components are using dynamic sizes. This means that
each element's exact dimensions are unknown when rendered. An estimated
dimension is used to get an a initial measurement, then this measurement
is readjusted on the fly as each element is rendered. Each component has
padding at the beginning and end of its scroll container.
@for (row of virtualizer.getVirtualItems(); track row.index) {
Row {{ row.index }}
}
`,
styles: `
.scroll-container {
height: 200px;
width: 400px;
overflow-y: auto;
}
`,
})
export class RowVirtualizerPadding {
rows = input.required()
scrollElement = viewChild>('scrollElement')
virtualItems = viewChildren>('virtualItem')
#measureItems = effect(
() =>
this.virtualItems().forEach((el) => {
this.virtualizer.measureElement(el.nativeElement)
}),
{ allowSignalWrites: true },
)
virtualizer = injectVirtualizer(() => ({
scrollElement: this.scrollElement(),
count: this.rows().length,
estimateSize: () => 50,
paddingStart: 100,
paddingEnd: 100,
}))
}
================================================
FILE: examples/angular/padding/src/index.html
================================================
@tanstack/virtualExampleAngularPadding
================================================
FILE: examples/angular/padding/src/main.ts
================================================
import { bootstrapApplication } from '@angular/platform-browser'
import { AppComponent } from './app/app.component'
bootstrapApplication(AppComponent).catch((err) => console.error(err))
================================================
FILE: examples/angular/padding/src/styles.css
================================================
*,
*:before,
*:after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
.list-item-odd {
background-color: #fff;
}
button {
border: 1px solid gray;
}
================================================
FILE: examples/angular/padding/tsconfig.app.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"]
}
================================================
FILE: examples/angular/padding/tsconfig.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": ["ES2022", "dom"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
================================================
FILE: examples/angular/smooth-scroll/.devcontainer/devcontainer.json
================================================
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:18"
}
================================================
FILE: examples/angular/smooth-scroll/.gitignore
================================================
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
================================================
FILE: examples/angular/smooth-scroll/README.md
================================================
# @tanstack/virtualExampleAngularSmoothScroll
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
================================================
FILE: examples/angular/smooth-scroll/angular.json
================================================
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"cli": {
"packageManager": "pnpm",
"analytics": false,
"cache": {
"enabled": false
}
},
"projects": {
"@tanstack/virtual-example-angular-smooth-scroll": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/tanstack/virtual-example-angular-smooth-scroll",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico"],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "@tanstack/virtual-example-angular-smooth-scroll:build:production"
},
"development": {
"buildTarget": "@tanstack/virtual-example-angular-smooth-scroll:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "@tanstack/virtual-example-angular-smooth-scroll:build"
}
}
}
}
}
}
================================================
FILE: examples/angular/smooth-scroll/package.json
================================================
{
"name": "@tanstack/virtual-example-angular-smooth-scroll",
"private": true,
"type": "module",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"dependencies": {
"@angular/animations": "^18.1.0",
"@angular/common": "^18.1.0",
"@angular/compiler": "^18.1.0",
"@angular/core": "^18.1.0",
"@angular/forms": "^18.1.0",
"@angular/platform-browser": "^18.1.0",
"@angular/platform-browser-dynamic": "^18.1.0",
"@angular/router": "^18.1.0",
"@tanstack/angular-virtual": "^4.0.11",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.1.0",
"@angular/cli": "^18.1.0",
"@angular/compiler-cli": "^18.1.0",
"typescript": "5.4.5"
}
}
================================================
FILE: examples/angular/smooth-scroll/src/app/app.component.ts
================================================
import {
ChangeDetectionStrategy,
Component,
ElementRef,
signal,
viewChild,
} from '@angular/core'
import { elementScroll, injectVirtualizer } from '@tanstack/angular-virtual'
function easeInOutQuint(t: number) {
return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t
}
@Component({
selector: 'app-root',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
This smooth scroll example uses the scrollToFn to implement a
custom scrolling function for the methods like
scrollToIndex and scrollToOffset
@for (row of virtualizer.getVirtualItems(); track row.index) {
Row {{ row.index }}
}
`,
styles: `
.scroll-container {
height: 200px;
width: 400px;
overflow: auto;
}
`,
})
export class AppComponent {
scrollElement = viewChild>('scrollElement')
scrollingTime = signal(0)
virtualizer = injectVirtualizer(() => ({
scrollElement: this.scrollElement(),
count: 10000,
estimateSize: () => 35,
overscan: 5,
scrollToFn: (offset, options, instance) => {
const duration = 1000
const start = this.scrollElement()!.nativeElement.scrollTop
const startTime = Date.now()
this.scrollingTime.set(startTime)
const run = () => {
if (this.scrollingTime() !== startTime) return
const now = Date.now()
const elapsed = now - startTime
const progress = easeInOutQuint(Math.min(elapsed / duration, 1))
const interpolated = start + (offset - start) * progress
if (elapsed < duration) {
elementScroll(interpolated, options, instance)
requestAnimationFrame(run)
} else {
elementScroll(interpolated, options, instance)
}
}
requestAnimationFrame(run)
},
}))
randomIndex = signal(Math.floor(Math.random() * 10000))
scrollToRandomIndex() {
this.virtualizer.scrollToIndex(this.randomIndex())
this.randomIndex.set(Math.floor(Math.random() * 10000))
}
}
================================================
FILE: examples/angular/smooth-scroll/src/index.html
================================================
@tanstack/virtualExampleAngularSmoothScroll
================================================
FILE: examples/angular/smooth-scroll/src/main.ts
================================================
import { bootstrapApplication } from '@angular/platform-browser'
import { AppComponent } from './app/app.component'
bootstrapApplication(AppComponent).catch((err) => console.error(err))
================================================
FILE: examples/angular/smooth-scroll/src/styles.css
================================================
*,
*:before,
*:after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
.list-item-odd {
background-color: #fff;
}
button {
border: 1px solid gray;
}
================================================
FILE: examples/angular/smooth-scroll/tsconfig.app.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"]
}
================================================
FILE: examples/angular/smooth-scroll/tsconfig.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": ["ES2022", "dom"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
================================================
FILE: examples/angular/sticky/.devcontainer/devcontainer.json
================================================
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:18"
}
================================================
FILE: examples/angular/sticky/.gitignore
================================================
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
================================================
FILE: examples/angular/sticky/README.md
================================================
# @tanstack/virtualExampleAngularSticky
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
================================================
FILE: examples/angular/sticky/angular.json
================================================
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"cli": {
"packageManager": "pnpm",
"analytics": false,
"cache": {
"enabled": false
}
},
"projects": {
"@tanstack/virtual-example-angular-sticky": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/tanstack/virtual-example-angular-sticky",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico"],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "@tanstack/virtual-example-angular-sticky:build:production"
},
"development": {
"buildTarget": "@tanstack/virtual-example-angular-sticky:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "@tanstack/virtual-example-angular-sticky:build"
}
}
}
}
}
}
================================================
FILE: examples/angular/sticky/package.json
================================================
{
"name": "@tanstack/virtual-example-angular-sticky",
"private": true,
"type": "module",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"dependencies": {
"@angular/animations": "^18.1.0",
"@angular/common": "^18.1.0",
"@angular/compiler": "^18.1.0",
"@angular/core": "^18.1.0",
"@angular/forms": "^18.1.0",
"@angular/platform-browser": "^18.1.0",
"@angular/platform-browser-dynamic": "^18.1.0",
"@angular/router": "^18.1.0",
"@faker-js/faker": "^8.4.1",
"@tanstack/angular-virtual": "^4.0.11",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.1.0",
"@angular/cli": "^18.1.0",
"@angular/compiler-cli": "^18.1.0",
"typescript": "5.4.5"
}
}
================================================
FILE: examples/angular/sticky/src/app/app.component.ts
================================================
import {
ChangeDetectionStrategy,
Component,
ElementRef,
computed,
viewChild,
} from '@angular/core'
import { faker } from '@faker-js/faker'
import {
injectVirtualizer,
defaultRangeExtractor,
} from '@tanstack/angular-virtual'
const groupedNames: Record = {}
Array.from({ length: 1000 })
.map(() => faker.person.firstName())
.sort()
.forEach((name) => {
const char = name[0]
if (!groupedNames[char]) {
groupedNames[char] = []
}
groupedNames[char].push(name)
})
const groups = Object.keys(groupedNames)
const rows = groups.reduce(
(acc: string[], k) => [...acc, k, ...groupedNames[k]],
[],
)
const stickyIndexes = groups.map((gn) => rows.findIndex((n) => n === gn))
const stickyIndexesSet = new Set(stickyIndexes)
const reversedStickyIndexes = [...stickyIndexes].reverse()
@Component({
selector: 'app-root',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@for (row of virtualizer.getVirtualItems(); track row.index) {
{{ rows[row.index] }}
}
`,
styles: `
.scroll-container {
height: 300px;
width: 400px;
overflow: auto;
}
`,
})
export class AppComponent {
rows = rows
isSticky = (index: number) => stickyIndexesSet.has(index)
scrollElement = viewChild>('scrollElement')
virtualizer = injectVirtualizer(() => ({
scrollElement: this.scrollElement(),
count: this.rows.length,
estimateSize: () => 50,
rangeExtractor: (range) => {
const next = new Set([
reversedStickyIndexes.find((index) => range.startIndex >= index)!,
...defaultRangeExtractor(range),
])
return [...next].sort((a, b) => a - b)
},
}))
activeStickyIndex = computed(() => {
return this.virtualizer.getVirtualItems()[0]?.index
})
isActiveSticky = (index: number) => this.activeStickyIndex() === index
}
================================================
FILE: examples/angular/sticky/src/index.html
================================================
@tanstack/virtualExampleAngularSticky
================================================
FILE: examples/angular/sticky/src/main.ts
================================================
import { bootstrapApplication } from '@angular/platform-browser'
import { AppComponent } from './app/app.component'
bootstrapApplication(AppComponent).catch((err) => console.error(err))
================================================
FILE: examples/angular/sticky/src/styles.css
================================================
*,
*:before,
*:after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
.list-item-odd {
background-color: #fff;
}
button {
border: 1px solid gray;
}
================================================
FILE: examples/angular/sticky/tsconfig.app.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"]
}
================================================
FILE: examples/angular/sticky/tsconfig.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": ["ES2022", "dom"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
================================================
FILE: examples/angular/table/.devcontainer/devcontainer.json
================================================
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:18"
}
================================================
FILE: examples/angular/table/.gitignore
================================================
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
================================================
FILE: examples/angular/table/README.md
================================================
# @tanstack/virtualExampleAngularTable
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
================================================
FILE: examples/angular/table/angular.json
================================================
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"cli": {
"packageManager": "pnpm",
"analytics": false,
"cache": {
"enabled": false
}
},
"projects": {
"@tanstack/virtual-example-angular-table": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/tanstack/virtual-example-angular-table",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico"],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "@tanstack/virtual-example-angular-table:build:production"
},
"development": {
"buildTarget": "@tanstack/virtual-example-angular-table:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "@tanstack/virtual-example-angular-table:build"
}
}
}
}
}
}
================================================
FILE: examples/angular/table/package.json
================================================
{
"name": "@tanstack/virtual-example-angular-table",
"private": true,
"type": "module",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"dependencies": {
"@angular/animations": "^18.1.0",
"@angular/common": "^18.1.0",
"@angular/compiler": "^18.1.0",
"@angular/core": "^18.1.0",
"@angular/forms": "^18.1.0",
"@angular/platform-browser": "^18.1.0",
"@angular/platform-browser-dynamic": "^18.1.0",
"@angular/router": "^18.1.0",
"@faker-js/faker": "^8.4.1",
"@tanstack/angular-table": "8.21.3",
"@tanstack/angular-virtual": "^4.0.11",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.1.0",
"@angular/cli": "^18.1.0",
"@angular/compiler-cli": "^18.1.0",
"typescript": "5.4.5"
}
}
================================================
FILE: examples/angular/table/src/app/app.component.ts
================================================
import {
ChangeDetectionStrategy,
Component,
ElementRef,
computed,
signal,
viewChild,
} from '@angular/core'
import { injectVirtualizer } from '@tanstack/angular-virtual'
import {
ColumnDef,
createAngularTable,
getCoreRowModel,
getSortedRowModel,
SortingState,
FlexRenderDirective,
SortDirection,
} from '@tanstack/angular-table'
import { makeData, type Person } from './make-data'
@Component({
selector: 'app-root',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [FlexRenderDirective],
template: `
For tables, the basis for the offset of the translate css function is from
the row's initial position itself. Because of this, we need to calculate
the translateY pixel count different and base it off the the index.
@for (
headerGroup of table.getHeaderGroups();
track headerGroup.id
) {
@for (header of headerGroup.headers; track header.id) {
@for (row of virtualizer.getVirtualItems(); track row.index) {
Row {{ row.index }}
}
`,
styles: `
.scroll-container {
height: 200px;
width: 400px;
overflow: auto;
}
`,
})
export class RowVirtualizerVariable {
rows = input.required()
scrollElement = viewChild>('scrollElement')
virtualizer = injectVirtualizer(() => ({
scrollElement: this.scrollElement(),
count: this.rows().length,
estimateSize: (index) => this.rows()[index]!,
overscan: 5,
}))
}
================================================
FILE: examples/angular/variable/src/index.html
================================================
@tanstack/virtualExampleAngularVariable
================================================
FILE: examples/angular/variable/src/main.ts
================================================
import { bootstrapApplication } from '@angular/platform-browser'
import { AppComponent } from './app/app.component'
bootstrapApplication(AppComponent).catch((err) => console.error(err))
================================================
FILE: examples/angular/variable/src/styles.css
================================================
*,
*:before,
*:after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
.list-item-odd {
background-color: #fff;
}
button {
border: 1px solid gray;
}
================================================
FILE: examples/angular/variable/tsconfig.app.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"]
}
================================================
FILE: examples/angular/variable/tsconfig.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": ["ES2022", "dom"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
================================================
FILE: examples/angular/window/.devcontainer/devcontainer.json
================================================
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:18"
}
================================================
FILE: examples/angular/window/.gitignore
================================================
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
================================================
FILE: examples/angular/window/README.md
================================================
# @tanstack/virtualExampleAngularWindow
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
================================================
FILE: examples/angular/window/angular.json
================================================
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"cli": {
"packageManager": "pnpm",
"analytics": false,
"cache": {
"enabled": false
}
},
"projects": {
"@tanstack/virtual-example-angular-window": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/tanstack/virtual-example-angular-window",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico"],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "@tanstack/virtual-example-angular-window:build:production"
},
"development": {
"buildTarget": "@tanstack/virtual-example-angular-window:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "@tanstack/virtual-example-angular-window:build"
}
}
}
}
}
}
================================================
FILE: examples/angular/window/package.json
================================================
{
"name": "@tanstack/virtual-example-angular-window",
"private": true,
"type": "module",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"dependencies": {
"@angular/animations": "^18.1.0",
"@angular/common": "^18.1.0",
"@angular/compiler": "^18.1.0",
"@angular/core": "^18.1.0",
"@angular/forms": "^18.1.0",
"@angular/platform-browser": "^18.1.0",
"@angular/platform-browser-dynamic": "^18.1.0",
"@angular/router": "^18.1.0",
"@tanstack/angular-virtual": "^4.0.11",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.1.0",
"@angular/cli": "^18.1.0",
"@angular/compiler-cli": "^18.1.0",
"typescript": "5.4.5"
}
}
================================================
FILE: examples/angular/window/src/app/app.component.ts
================================================
import {
ChangeDetectionStrategy,
Component,
ElementRef,
viewChild,
} from '@angular/core'
import { injectWindowVirtualizer } from '@tanstack/angular-virtual'
@Component({
selector: 'app-root',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
In many cases, when implementing a virtualizer with a window as the
scrolling element, developers often find the need to specify a
"scrollMargin." The scroll margin is a crucial setting that defines the
space or gap between the start of the page and the edges of the list.
Window Scroller
@for (row of virtualizer.getVirtualItems(); track row.key) {
These components are using dynamic sizes. This means
that each element's exact dimensions are unknown when rendered. An
estimated dimension is used as the initial measurement, then this
measurement is readjusted on the fly as each element is rendered.
${(() => {
switch (pathname) {
case '/':
return html``
case '/columns':
return html``
case '/grid': {
const columns = generateColumns(30)
const data = generateData(columns)
return html``
}
default:
return html`
These components are using dynamic sizes. This means
that each element's exact dimensions are unknown when rendered. An
estimated dimension is used as the initial measurement, then this
measurement is readjusted on the fly as each element is rendered.
{(() => {
switch (pathname) {
case '/':
return
case '/columns':
return
case '/grid': {
const columns = generateColumns(30)
const data = generateData(columns)
return
}
case '/experimental':
return
default:
return
Not found
}
})()}
{process.env.NODE_ENV === 'development' ? (
Notice: You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
These components are using fixed sizes. This means that
every element's dimensions are hard-coded to the same value and never
change.
Rows
Columns
Grid
{process.env.NODE_ENV === 'development' ? (
Notice: You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
This infinite scroll example uses React Query's useInfiniteScroll hook
to fetch infinite data from a posts endpoint and then a rowVirtualizer
is used along with a loader-row placed at the bottom of the list to
trigger the next page to load.
Notice: You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
These components are using dynamic sizes. This means
that each element's exact dimensions are unknown when rendered. An
estimated dimension is used as the initial measurement, then this
measurement is readjusted on the fly as each element is rendered.
Notice: You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
Notice: You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
This smooth scroll example uses the `scrollToFn` to
implement a custom scrolling function for the methods like{' '}
`scrollToIndex` and `scrollToOffset`
Notice: You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
Notice: You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
For tables, the basis for the offset of the translate css function is
from the row's initial position itself. Because of this, we need to
calculate the translateY pixel count differently and base it off the
index.
{process.env.NODE_ENV === 'development' ? (
Notice: You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
These components are using variable sizes. This means
that each element has a unique, but knowable dimension at render time.
Rows
Columns
Grid
Masonry (vertical)
Masonry (horizontal)
{process.env.NODE_ENV === 'development' ? (
Notice: You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
In many cases, when implementing a virtualizer with a window as the
scrolling element, developers often find the need to specify a
"scrollMargin." The scroll margin is a crucial setting that defines the
space or gap between the start of the page and the edges of the list.
Window scroller
{process.env.NODE_ENV === 'development' ? (
Notice: You are currently running React in
development mode. Rendering performance will be slightly degraded
until this application is built for production.
These components are using dynamic sizes. This means that each
element's exact dimensions are unknown when rendered. An estimated dimension is
used as the initial measurement, then this measurement is readjusted on the fly
as each element is rendered.
{#if pathname === '/'}
{:else if pathname === '/window-list'}
{:else if pathname === '/columns'}
{:else if pathname === '/grid'}
{:else}
{#each $virtualizer.getVirtualItems() as row (row.index)}
Row {row.index}
{/each}
================================================
FILE: examples/svelte/fixed/src/app.css
================================================
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
button {
border: 1px solid gray;
}
================================================
FILE: examples/svelte/fixed/src/main.ts
================================================
import './app.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')!,
})
export default app
================================================
FILE: examples/svelte/fixed/src/vite-env.d.ts
================================================
///
///
================================================
FILE: examples/svelte/fixed/svelte.config.js
================================================
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}
================================================
FILE: examples/svelte/fixed/tsconfig.json
================================================
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}
================================================
FILE: examples/svelte/fixed/tsconfig.node.json
================================================
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler"
},
"include": ["vite.config.ts"]
}
================================================
FILE: examples/svelte/fixed/vite.config.ts
================================================
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
})
================================================
FILE: examples/svelte/infinite-scroll/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: examples/svelte/infinite-scroll/README.md
================================================
# Example
To run this example:
- `npm install` or `yarn`
- `npm run dev` or `yarn dev`
================================================
FILE: examples/svelte/infinite-scroll/index.html
================================================
================================================
FILE: examples/svelte/infinite-scroll/package.json
================================================
{
"name": "tanstack-svelte-virtual-example-infinite-scroll",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"dependencies": {
"@tanstack/svelte-query": "^5.80.7",
"@tanstack/svelte-virtual": "^3.13.23"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@tsconfig/svelte": "^5.0.4",
"svelte": "^4.2.20",
"svelte-check": "^4.2.1",
"tslib": "^2.8.1",
"typescript": "5.4.5",
"vite": "^5.4.19"
}
}
================================================
FILE: examples/svelte/infinite-scroll/src/App.svelte
================================================
================================================
FILE: examples/svelte/infinite-scroll/src/InfiniteRows.svelte
================================================
This infinite scroll example uses Svelte Query's createInfiniteQuery
function to fetch infinite data from a posts endpoint and then a
rowVirtualizer is used along with a loader-row placed at the bottom of the
list to trigger the next page to load.
{#if $query.isLoading}
Loading...
{:else if $query.isError}
Error: {$query.error.message}
{:else if $query.isSuccess}
{#each $virtualizer.getVirtualItems() as row (row.index)}
{#if row.index > allRows.length - 1}
{#if $query.hasNextPage}
Loading more...
{:else}
Nothing more to load
{/if}
{:else}
{allRows[row.index]}
{/if}
{/if}
================================================
FILE: examples/svelte/infinite-scroll/src/app.css
================================================
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
button {
border: 1px solid gray;
}
================================================
FILE: examples/svelte/infinite-scroll/src/main.ts
================================================
import './app.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')!,
})
export default app
================================================
FILE: examples/svelte/infinite-scroll/src/vite-env.d.ts
================================================
///
///
================================================
FILE: examples/svelte/infinite-scroll/svelte.config.js
================================================
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}
================================================
FILE: examples/svelte/infinite-scroll/tsconfig.json
================================================
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}
================================================
FILE: examples/svelte/infinite-scroll/tsconfig.node.json
================================================
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler"
},
"include": ["vite.config.ts"]
}
================================================
FILE: examples/svelte/infinite-scroll/vite.config.ts
================================================
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
})
================================================
FILE: examples/svelte/smooth-scroll/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: examples/svelte/smooth-scroll/README.md
================================================
# Example
To run this example:
- `npm install` or `yarn`
- `npm run dev` or `yarn dev`
================================================
FILE: examples/svelte/smooth-scroll/index.html
================================================
================================================
FILE: examples/svelte/smooth-scroll/package.json
================================================
{
"name": "tanstack-svelte-virtual-example-smooth-scroll",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"dependencies": {
"@faker-js/faker": "^8.4.1",
"@tanstack/svelte-virtual": "^3.13.23"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@tsconfig/svelte": "^5.0.4",
"svelte": "^4.2.20",
"svelte-check": "^4.2.1",
"tslib": "^2.8.1",
"typescript": "5.4.5",
"vite": "^5.4.19"
}
}
================================================
FILE: examples/svelte/smooth-scroll/src/App.svelte
================================================
This smooth scroll example uses the `scrollToFn` to implement a
custom scrolling function for the methods like
`scrollToIndex` and `scrollToOffset`
{#each $virtualizer.getVirtualItems() as row (row.index)}
Row {row.index}
{/each}
================================================
FILE: examples/svelte/smooth-scroll/src/app.css
================================================
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
button {
border: 1px solid gray;
}
================================================
FILE: examples/svelte/smooth-scroll/src/main.ts
================================================
import './app.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')!,
})
export default app
================================================
FILE: examples/svelte/smooth-scroll/src/vite-env.d.ts
================================================
///
///
================================================
FILE: examples/svelte/smooth-scroll/svelte.config.js
================================================
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}
================================================
FILE: examples/svelte/smooth-scroll/tsconfig.json
================================================
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}
================================================
FILE: examples/svelte/smooth-scroll/tsconfig.node.json
================================================
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler"
},
"include": ["vite.config.ts"]
}
================================================
FILE: examples/svelte/smooth-scroll/vite.config.ts
================================================
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
})
================================================
FILE: examples/svelte/sticky/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: examples/svelte/sticky/README.md
================================================
# Example
To run this example:
- `npm install` or `yarn`
- `npm run dev` or `yarn dev`
================================================
FILE: examples/svelte/sticky/index.html
================================================
================================================
FILE: examples/svelte/sticky/package.json
================================================
{
"name": "tanstack-svelte-virtual-example-sticky",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"dependencies": {
"@faker-js/faker": "^8.4.1",
"@tanstack/svelte-virtual": "^3.13.23",
"lodash": "^4.17.21"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@tsconfig/svelte": "^5.0.4",
"svelte": "^4.2.20",
"svelte-check": "^4.2.1",
"tslib": "^2.8.1",
"typescript": "5.4.5",
"vite": "^5.4.19"
}
}
================================================
FILE: examples/svelte/sticky/src/App.svelte
================================================
{#each $virtualizer.getVirtualItems() as row (row.index)}
{rows[row.index]}
{/each}
================================================
FILE: examples/svelte/sticky/src/app.css
================================================
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
button {
border: 1px solid gray;
}
================================================
FILE: examples/svelte/sticky/src/main.ts
================================================
import './app.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')!,
})
export default app
================================================
FILE: examples/svelte/sticky/src/vite-env.d.ts
================================================
///
///
================================================
FILE: examples/svelte/sticky/svelte.config.js
================================================
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}
================================================
FILE: examples/svelte/sticky/tsconfig.json
================================================
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}
================================================
FILE: examples/svelte/sticky/tsconfig.node.json
================================================
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler"
},
"include": ["vite.config.ts"]
}
================================================
FILE: examples/svelte/sticky/vite.config.ts
================================================
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
})
================================================
FILE: examples/svelte/table/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: examples/svelte/table/README.md
================================================
# Example
To run this example:
- `npm install` or `yarn`
- `npm run dev` or `yarn dev`
================================================
FILE: examples/svelte/table/index.html
================================================
================================================
FILE: examples/svelte/table/package.json
================================================
{
"name": "tanstack-svelte-virtual-example-table",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"dependencies": {
"@faker-js/faker": "^8.4.1",
"@tanstack/svelte-table": "^8.21.3",
"@tanstack/svelte-virtual": "^3.13.23"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@tsconfig/svelte": "^5.0.4",
"svelte": "^4.2.20",
"svelte-check": "^4.2.1",
"tslib": "^2.8.1",
"typescript": "5.4.5",
"vite": "^5.4.19"
}
}
================================================
FILE: examples/svelte/table/src/App.svelte
================================================
For tables, the basis for the offset of the translate css function is from
the row's initial position itself. Because of this, we need to calculate the
translateY pixel count differently and base it off the index.
{#each $table.getHeaderGroups() as headerGroup (headerGroup.id)}
{#each headerGroup.headers as header (header.id)}
{#if !header.isPlaceholder}
{/if}
{/each}
{/each}
{#each $virtualizer.getVirtualItems() as row, idx (row.index)}
{#each rows[row.index].getVisibleCells() as cell (cell.id)}
{/each}
{/each}
================================================
FILE: examples/svelte/table/src/app.css
================================================
html {
font-family: sans-serif;
font-size: 14px;
}
body {
padding: 1rem;
}
.list {
border: 1px solid #e6e4dc;
max-width: 100%;
}
.list-item-even,
.list-item-odd {
display: flex;
align-items: center;
justify-content: center;
}
.list-item-even {
background-color: #e6e4dc;
}
button {
border: 1px solid gray;
}
================================================
FILE: examples/svelte/table/src/main.ts
================================================
import './app.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')!,
})
export default app
================================================
FILE: examples/svelte/table/src/makeData.ts
================================================
import { faker } from '@faker-js/faker'
export type Person = {
id: number
firstName: string
lastName: string
age: number
visits: number
progress: number
status: 'relationship' | 'complicated' | 'single'
createdAt: Date
}
const range = (len: number) => {
const arr: number[] = []
for (let i = 0; i < len; i++) {
arr.push(i)
}
return arr
}
const newPerson = (index: number): Person => {
return {
id: index + 1,
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
age: faker.number.int(40),
visits: faker.number.int(1000),
progress: faker.number.int(100),
createdAt: faker.datatype.datetime({ max: new Date().getTime() }),
status: faker.helpers.shuffle([
'relationship',
'complicated',
'single',
])[0]!,
}
}
export function makeData(...lens: number[]) {
const makeDataLevel = (depth = 0): Person[] => {
const len = lens[depth]!
return range(len).map((d): Person => {
return {
...newPerson(d),
}
})
}
return makeDataLevel()
}
================================================
FILE: examples/svelte/table/src/vite-env.d.ts
================================================
///
///
================================================
FILE: examples/svelte/table/svelte.config.js
================================================
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}
================================================
FILE: examples/svelte/table/tsconfig.json
================================================
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}
================================================
FILE: examples/svelte/table/tsconfig.node.json
================================================
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler"
},
"include": ["vite.config.ts"]
}
================================================
FILE: examples/svelte/table/vite.config.ts
================================================
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
})
================================================
FILE: examples/vue/dynamic/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: examples/vue/dynamic/README.md
================================================
# Example
To run this example:
- `npm install` or `yarn`
- `npm run dev` or `yarn dev`
================================================
FILE: examples/vue/dynamic/index.html
================================================
Vite + Vue + TS
================================================
FILE: examples/vue/dynamic/package.json
================================================
{
"name": "tanstack-vue-virtual-example-dynamic",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@faker-js/faker": "^8.4.1",
"@tanstack/vue-virtual": "^3.13.23",
"vue": "^3.5.16"
},
"devDependencies": {
"@codesandbox/vue-preview": "^0.1.1-alpha.16",
"@vitejs/plugin-vue": "^5.2.4",
"typescript": "5.4.5",
"vite": "^5.4.19",
"vue-tsc": "^2.2.10"
}
}
================================================
FILE: examples/vue/dynamic/src/App.vue
================================================
These components are using dynamic sizes. This means that
each element's exact dimensions are unknown when rendered. An estimated
dimension is used as the initial measurement, then this measurement is
readjusted on the fly as each element is rendered.
This infinite scroll example uses Vue Query's useInfiniteScroll composable
to fetch infinite data from a posts endpoint and then a rowVirtualizer is
used along with a loader-row placed at the bottom of the list to trigger
the next page to load.
Loading...
Error: {{ (error as Error).message }}
{{ hasNextPage ? 'Loading more...' : 'Nothing more to load' }}
{{ allRows[virtualRow.index] }}
For tables, the basis for the offset of the translate css function is from
the row's initial position itself. Because of this, we need to calculate
the translateY pixel count differently and base it off the index.
@for (row of virtualizer.getVirtualItems(); track row.index) {
Row {{ row.index }}
}
`,
})
export class MyVirtualizedList {
scrollElement = viewChild>('scrollElement')
virtualizer = injectVirtualizer(() => ({
scrollElement: this.scrollElement(),
count: 1000,
estimateSize: () => 35,
overscan: 5,
}))
}
```
Note that a [ViewChild](https://angular.dev/api/core/viewChild) is used to get a reference to the scrolling container to allow the virtualizer to interact with it. The adapter will automatically unwrap the [ElementRef](https://angular.dev/api/core/ElementRef) for you.
You can also create a virtualizer that attaches to the Window with `injectWindowVirtualizer`:
```ts
import { Component } from '@angular/core'
import { injectWindowVirtualizer } from '@tanstack/angular-virtual'
@Component({
selector: 'my-window-virtualized-list',
template: `
@for (row of virtualizer.getVirtualItems(); track row.index) {
Row {{ row.index }}
}
`,
})
export class MyWindowVirtualizedList {
virtualizer = injectWindowVirtualizer(() => ({
count: 1000,
estimateSize: () => 35,
overscan: 5,
}))
}
```
3. If you need to update options on your virtualizer dynamically, make sure to use signals.
```ts
import { Component, input } from '@angular/core'
import { injectVirtualizer } from '@tanstack/angular-virtual'
@Component({...})
export class MyVirtualizedList {
items = input>()
virtualizer = injectVirtualizer(() => ({
scrollElement: this.scrollElement(),
count: this.items().length,
estimateSize: () => 35,
overscan: 5,
}))
}
```
For more examples and detailed usage, visit the [official documentation](https://tanstack.com/virtual/latest).
================================================
FILE: packages/angular-virtual/eslint.config.js
================================================
// @ts-check
import rootConfig from '../../eslint.config.js'
export default [...rootConfig]
================================================
FILE: packages/angular-virtual/ng-package.json
================================================
{
"$schema": "./node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/index.ts"
},
"allowedNonPeerDependencies": ["@tanstack/virtual-core"],
"dest": "build",
"deleteDestPath": false
}
================================================
FILE: packages/angular-virtual/package.json
================================================
{
"name": "@tanstack/angular-virtual",
"version": "4.0.11",
"description": "Headless UI for virtualizing scrollable elements in Angular",
"author": "Garrett Darnell",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/TanStack/virtual.git",
"directory": "packages/angular-virtual"
},
"homepage": "https://tanstack.com/virtual",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"keywords": [
"angular",
"virtual",
"virtual-core"
],
"type": "module",
"module": "build/fesm2022/tanstack-angular-virtual.mjs",
"types": "build/index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"esm2022": "./build/esm2022/tanstack-angular-virtual.mjs",
"esm": "./build/esm2022/tanstack-angular-virtual.mjs",
"default": "./build/fesm2022/tanstack-angular-virtual.mjs"
},
"./package.json": {
"default": "./package.json"
}
},
"engines": {
"node": ">=12"
},
"files": [
"build"
],
"scripts": {
"clean": "premove ./build",
"test:types": "tsc --noEmit",
"test:eslint": "eslint ./src",
"build": "ng-packagr -p ng-package.json -c tsconfig.build.json"
},
"dependencies": {
"@tanstack/virtual-core": "workspace:*",
"tslib": "^2.8.1"
},
"devDependencies": {
"@angular/core": "^18.1.0",
"ng-packagr": "^18.1.0",
"typescript": "5.4.5"
},
"peerDependencies": {
"@angular/core": ">=18.1.0"
},
"sideEffects": false
}
================================================
FILE: packages/angular-virtual/src/index.ts
================================================
import {
DestroyRef,
afterNextRender,
computed,
effect,
inject,
signal,
untracked,
} from '@angular/core'
import {
Virtualizer,
elementScroll,
observeElementOffset,
observeElementRect,
observeWindowOffset,
observeWindowRect,
windowScroll,
} from '@tanstack/virtual-core'
import { proxyVirtualizer } from './proxy'
import type { ElementRef, Signal } from '@angular/core'
import type { PartialKeys, VirtualizerOptions } from '@tanstack/virtual-core'
import type { AngularVirtualizer } from './types'
export * from '@tanstack/virtual-core'
export * from './types'
function createVirtualizerBase<
TScrollElement extends Element | Window,
TItemElement extends Element,
>(
options: Signal>,
): AngularVirtualizer {
let virtualizer: Virtualizer
function lazyInit() {
virtualizer ??= new Virtualizer(options())
return virtualizer
}
const virtualizerSignal = signal(virtualizer!, { equal: () => false })
// two-way sync options
effect(
() => {
const _options = options()
lazyInit()
virtualizerSignal.set(virtualizer)
virtualizer.setOptions({
..._options,
onChange: (instance, sync) => {
// update virtualizerSignal so that dependent computeds recompute.
virtualizerSignal.set(instance)
_options.onChange?.(instance, sync)
},
})
// update virtualizerSignal so that dependent computeds recompute.
virtualizerSignal.set(virtualizer)
},
{ allowSignalWrites: true },
)
const scrollElement = computed(() => options().getScrollElement())
// let the virtualizer know when the scroll element is changed
effect(
() => {
const el = scrollElement()
if (el) {
untracked(virtualizerSignal)._willUpdate()
}
},
{ allowSignalWrites: true },
)
let cleanup: () => void | undefined
afterNextRender({ read: () => (virtualizer ?? lazyInit())._didMount() })
inject(DestroyRef).onDestroy(() => cleanup?.())
return proxyVirtualizer(virtualizerSignal, lazyInit)
}
export function injectVirtualizer<
TScrollElement extends Element,
TItemElement extends Element,
>(
options: () => PartialKeys<
Omit, 'getScrollElement'>,
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
> & {
scrollElement: ElementRef | TScrollElement | undefined
},
): AngularVirtualizer {
const resolvedOptions = computed(() => {
return {
observeElementRect: observeElementRect,
observeElementOffset: observeElementOffset,
scrollToFn: elementScroll,
getScrollElement: () => {
const elementOrRef = options().scrollElement
return (
(isElementRef(elementOrRef)
? elementOrRef.nativeElement
: elementOrRef) ?? null
)
},
...options(),
}
})
return createVirtualizerBase(resolvedOptions)
}
function isElementRef(
elementOrRef: ElementRef | T | undefined,
): elementOrRef is ElementRef {
return elementOrRef != null && 'nativeElement' in elementOrRef
}
export function injectWindowVirtualizer(
options: () => PartialKeys<
VirtualizerOptions,
| 'getScrollElement'
| 'observeElementRect'
| 'observeElementOffset'
| 'scrollToFn'
>,
): AngularVirtualizer {
const resolvedOptions = computed(() => {
return {
getScrollElement: () => (typeof document !== 'undefined' ? window : null),
observeElementRect: observeWindowRect,
observeElementOffset: observeWindowOffset,
scrollToFn: windowScroll,
initialOffset: () =>
typeof document !== 'undefined' ? window.scrollY : 0,
...options(),
}
})
return createVirtualizerBase(resolvedOptions)
}
================================================
FILE: packages/angular-virtual/src/proxy.ts
================================================
import { computed, untracked } from '@angular/core'
import type { Signal, WritableSignal } from '@angular/core'
import type { Virtualizer } from '@tanstack/virtual-core'
import type { AngularVirtualizer } from './types'
export function proxyVirtualizer<
V extends Virtualizer,
S extends Element | Window = V extends Virtualizer ? U : never,
I extends Element = V extends Virtualizer ? U : never,
>(
virtualizerSignal: WritableSignal,
lazyInit: () => V,
): AngularVirtualizer {
return new Proxy(virtualizerSignal, {
apply() {
return virtualizerSignal()
},
get(target, property) {
const untypedTarget = target as any
if (untypedTarget[property]) {
return untypedTarget[property]
}
let virtualizer = untracked(virtualizerSignal)
if (virtualizer == null) {
virtualizer = lazyInit()
untracked(() => virtualizerSignal.set(virtualizer))
}
// Create computed signals for each property that represents a reactive value
if (
typeof property === 'string' &&
[
'getTotalSize',
'getVirtualItems',
'isScrolling',
'options',
'range',
'scrollDirection',
'scrollElement',
'scrollOffset',
'scrollRect',
'measureElementCache',
'measurementsCache',
].includes(property)
) {
const isFunction =
typeof virtualizer[property as keyof V] === 'function'
Object.defineProperty(untypedTarget, property, {
value: isFunction
? computed(() => (target()[property as keyof V] as Function)())
: computed(() => target()[property as keyof V]),
configurable: true,
enumerable: true,
})
}
// Create plain signals for functions that accept arguments and return reactive values
if (
typeof property === 'string' &&
[
'getOffsetForAlignment',
'getOffsetForIndex',
'getVirtualItemForOffset',
'indexFromElement',
].includes(property)
) {
const fn = virtualizer[property as keyof V] as Function
Object.defineProperty(untypedTarget, property, {
value: toComputed(virtualizerSignal, fn),
configurable: true,
enumerable: true,
})
}
return untypedTarget[property] || virtualizer[property as keyof V]
},
has(_, property: string) {
return !!untracked(virtualizerSignal)[property as keyof V]
},
ownKeys() {
return Reflect.ownKeys(untracked(virtualizerSignal))
},
getOwnPropertyDescriptor() {
return {
enumerable: true,
configurable: true,
}
},
}) as unknown as AngularVirtualizer
}
function toComputed>(
signal: Signal,
fn: Function,
) {
const computedCache: Record> = {}
return (...args: Array) => {
// Cache computeds by their arguments to avoid re-creating the computed on each call
const serializedArgs = serializeArgs(...args)
if (computedCache.hasOwnProperty(serializedArgs)) {
return computedCache[serializedArgs]?.()
}
const computedSignal = computed(() => {
void signal()
return fn(...args)
})
computedCache[serializedArgs] = computedSignal
return computedSignal()
}
}
function serializeArgs(...args: Array) {
return JSON.stringify(args)
}
================================================
FILE: packages/angular-virtual/src/types.ts
================================================
import type { Signal } from '@angular/core'
import type { Virtualizer } from '@tanstack/virtual-core'
export type AngularVirtualizer<
TScrollElement extends Element | Window,
TItemElement extends Element,
> = Omit<
Virtualizer,
| 'getTotalSize'
| 'getVirtualItems'
| 'isScrolling'
| 'options'
| 'range'
| 'scrollDirection'
| 'scrollElement'
| 'scrollOffset'
| 'scrollRect'
> & {
getTotalSize: Signal<
ReturnType['getTotalSize']>
>
getVirtualItems: Signal<
ReturnType['getVirtualItems']>
>
isScrolling: Signal['isScrolling']>
options: Signal['options']>
range: Signal['range']>
scrollDirection: Signal<
Virtualizer['scrollDirection']
>
scrollElement: Signal<
Virtualizer['scrollElement']
>
scrollOffset: Signal<
Virtualizer['scrollOffset']
>
scrollRect: Signal['scrollRect']>
}
================================================
FILE: packages/angular-virtual/tsconfig.build.json
================================================
{
"extends": "./node_modules/ng-packagr/lib/ts/conf/tsconfig.ngc.json",
"compilerOptions": {
"moduleResolution": "bundler",
"allowJs": true,
"moduleDetection": "force",
"module": "ESNext"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true
},
"include": ["src/**/*.ts"]
}
================================================
FILE: packages/angular-virtual/tsconfig.json
================================================
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./build/lib"
},
"angularCompilerOptions": {
"compilationMode": "partial"
},
"include": ["src", "eslint.config.js"],
"exclude": ["**/*.spec.ts"]
}
================================================
FILE: packages/lit-virtual/CHANGELOG.md
================================================
# @tanstack/lit-virtual
## 3.13.24
### Patch Changes
- Updated dependencies [[`7ece2d5`](https://github.com/TanStack/virtual/commit/7ece2d5d4249b7e703c68ac497ae5545c54e7c67)]:
- @tanstack/virtual-core@3.13.23
## 3.13.23
### Patch Changes
- Updated dependencies [[`54d771a`](https://github.com/TanStack/virtual/commit/54d771a7d4c74f6968e8132b5a85f3e04682376a), [`d3416c3`](https://github.com/TanStack/virtual/commit/d3416c386c6446957f413db2eef3211f5fdf3b5f)]:
- @tanstack/virtual-core@3.13.22
## 3.13.22
### Patch Changes
- Updated dependencies [[`be89e29`](https://github.com/TanStack/virtual/commit/be89e293ea01654df6334dc6473b65eebed13e51)]:
- @tanstack/virtual-core@3.13.21
## 3.13.21
### Patch Changes
- Updated dependencies [[`ff83e94`](https://github.com/TanStack/virtual/commit/ff83e949408ba8a714436fa10cafc3725a56274b)]:
- @tanstack/virtual-core@3.13.20
## 3.13.20
### Patch Changes
- Updated dependencies [[`843109c`](https://github.com/TanStack/virtual/commit/843109c5bf780591a762f9767f3808fd15e3f94e)]:
- @tanstack/virtual-core@3.13.19
## 3.13.19
### Patch Changes
- Updated dependencies [[`9067574`](https://github.com/TanStack/virtual/commit/9067574f1a0178d30e27bcac70853bdcbf437fec)]:
- @tanstack/virtual-core@3.13.18
## 3.13.18
### Patch Changes
- Updated dependencies [[`21d9a46`](https://github.com/TanStack/virtual/commit/21d9a46eac034cb4299872891694965bceed526d)]:
- @tanstack/virtual-core@3.13.17
## 3.13.17
### Patch Changes
- Updated dependencies [[`db6df21`](https://github.com/TanStack/virtual/commit/db6df212ed83dd7e4eb6450d1340c95475667b7b)]:
- @tanstack/virtual-core@3.13.16
## 3.13.16
### Patch Changes
- Updated dependencies [[`5a273bf`](https://github.com/TanStack/virtual/commit/5a273bf0c0bc0255ca172929f021c3b6e50cb69d)]:
- @tanstack/virtual-core@3.13.15
## 3.13.15
### Patch Changes
- Updated dependencies [[`6d9274c`](https://github.com/TanStack/virtual/commit/6d9274c3f0a9e64450b5829872079a65277bc654)]:
- @tanstack/virtual-core@3.13.14
## 3.13.14
### Patch Changes
- Fix: Notify framework when count changes to update getTotalSize() ([#1085](https://github.com/TanStack/virtual/pull/1085))
Fixed an issue where `getTotalSize()` would return stale values when the `count` option changed (e.g., during filtering or search operations). The virtualizer now automatically notifies the framework when measurement-affecting options change, ensuring the UI updates correctly without requiring manual `useMemo` workarounds.
**Before**: When filtering items, the list container would maintain its previous height, causing excessive blank space (when count decreased) or inaccessible items (when count increased).
**After**: Height updates automatically when count changes, providing the correct user experience.
This fix applies to all framework adapters and has minimal performance impact (< 0.1ms per change).
- Updated dependencies [[`2542c5a`](https://github.com/TanStack/virtual/commit/2542c5a3d6820cea956fa3b4f94c42e3526a8d68), [`96e32a6`](https://github.com/TanStack/virtual/commit/96e32a6ffc125743a0172ea4e0fe37ac29c4187b)]:
- @tanstack/virtual-core@3.13.13
## 3.13.13
### Patch Changes
- fix(lit-virtual): create Virtualizer instance before hostConnected ([#1061](https://github.com/TanStack/virtual/pull/1061))
When creating an instance of the reactive controller in `connectedCallback`, calling `addController` will synchronously call `hostConnected` on the controller. This means that `this.virtualizer` will still be `undefined`.
## 3.13.12
### Patch Changes
- Updated dependencies [[`d21ed98`](https://github.com/TanStack/virtual/commit/d21ed98da3470b9986c9a028ed70fdf0d6189ab4)]:
- @tanstack/virtual-core@3.13.12
## 3.13.11
### Patch Changes
- Updated dependencies [[`73fa867`](https://github.com/TanStack/virtual/commit/73fa86752599a4bffba51ec8e4ff2f8cb8283010)]:
- @tanstack/virtual-core@3.13.11
## 3.13.10
### Patch Changes
- Updated dependencies [[`b3b7e7d`](https://github.com/TanStack/virtual/commit/b3b7e7dc8b25daeebbd2da61b3b7ae3448babbdb)]:
- @tanstack/virtual-core@3.13.10
## 3.13.9
### Patch Changes
- Updated dependencies [[`9e33cdb`](https://github.com/TanStack/virtual/commit/9e33cdb1c8780c2f455aafc11a0aeea58b71fc69)]:
- @tanstack/virtual-core@3.13.9
## 3.13.8
### Patch Changes
- Updated dependencies [[`60719f6`](https://github.com/TanStack/virtual/commit/60719f61b589d6f9d886e4f7c093217f6d693faf)]:
- @tanstack/virtual-core@3.13.8
## 3.13.7
### Patch Changes
- Updated dependencies [[`e2d93c2`](https://github.com/TanStack/virtual/commit/e2d93c2dcde9ccf60f658e56edccd8d05aefeee6)]:
- @tanstack/virtual-core@3.13.7
## 3.13.6
### Patch Changes
- Updated dependencies [[`042616f`](https://github.com/TanStack/virtual/commit/042616f39ced842470db0b4b40fca77f22454b7f)]:
- @tanstack/virtual-core@3.13.6
## 3.13.5
### Patch Changes
- Updated dependencies [[`51656d9`](https://github.com/TanStack/virtual/commit/51656d94a2469a065e631f25ffc8ec0288d9f5ec)]:
- @tanstack/virtual-core@3.13.5
## 3.13.4
### Patch Changes
- Updated dependencies [[`514b62d`](https://github.com/TanStack/virtual/commit/514b62d04974c2fd59fc8a68ed40f4c1a1547dd2), [`f03d814`](https://github.com/TanStack/virtual/commit/f03d8142c03ea0f5816161a4dad38ca35469841c)]:
- @tanstack/virtual-core@3.13.4
## 3.13.3
### Patch Changes
- Updated dependencies [[`02ef309`](https://github.com/TanStack/virtual/commit/02ef3097de4a14ed4077ace2ca901dc411bf81c1)]:
- @tanstack/virtual-core@3.13.3
================================================
FILE: packages/lit-virtual/README.md
================================================
# @tanstack/lit-virtual
Efficiently virtualize only the visible DOM nodes within massive scrollable elements using Lit, while maintaining complete control over markup and styles.
## `VirtualizerController`
`@tanstack/lit-virtual` utilizes [Reactive Controllers](https://lit.dev/docs/composition/controllers/) to create the virtualizer and integrate it with the element lifecycle:
```ts
import { LitElement } from 'lit'
import { VirtualizerController } from '@tanstack/lit-virtual'
import { Ref, createRef } from 'lit/directives/ref.js'
class MyVirtualElement extends LitElement {
private virtualizerController: VirtualizerController
private scrollElementRef: Ref = createRef()
constructor() {
super()
this.virtualizerController = new VirtualizerController(this, {
getScrollElement: () => this.scrollElementRef.value,
count: 10000,
estimateSize: () => 35,
overscan: 5,
})
}
render() {
const virtualizer = this.virtualizerController.getVirtualizer()
const virtualItems = virtualizer.getVirtualItems()
return html`