all these changes

This commit is contained in:
Jake Kasper
2026-04-09 13:19:47 -05:00
parent e83a51a051
commit 65315f36d1
39102 changed files with 7932979 additions and 567 deletions

21
frontend/node_modules/@tanstack/react-virtual/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
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.

View File

@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const React = require("react");
const reactDom = require("react-dom");
const virtualCore = require("@tanstack/virtual-core");
function _interopNamespaceDefault(e) {
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
if (e) {
for (const k in e) {
if (k !== "default") {
const d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: () => e[k]
});
}
}
}
n.default = e;
return Object.freeze(n);
}
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
const useIsomorphicLayoutEffect = typeof document !== "undefined" ? React__namespace.useLayoutEffect : React__namespace.useEffect;
function useVirtualizerBase({
useFlushSync = true,
...options
}) {
const rerender = React__namespace.useReducer(() => ({}), {})[1];
const resolvedOptions = {
...options,
onChange: (instance2, sync) => {
var _a;
if (useFlushSync && sync) {
reactDom.flushSync(rerender);
} else {
rerender();
}
(_a = options.onChange) == null ? void 0 : _a.call(options, instance2, sync);
}
};
const [instance] = React__namespace.useState(
() => new virtualCore.Virtualizer(resolvedOptions)
);
instance.setOptions(resolvedOptions);
useIsomorphicLayoutEffect(() => {
return instance._didMount();
}, []);
useIsomorphicLayoutEffect(() => {
return instance._willUpdate();
});
return instance;
}
function useVirtualizer(options) {
return useVirtualizerBase({
observeElementRect: virtualCore.observeElementRect,
observeElementOffset: virtualCore.observeElementOffset,
scrollToFn: virtualCore.elementScroll,
...options
});
}
function useWindowVirtualizer(options) {
return useVirtualizerBase({
getScrollElement: () => typeof document !== "undefined" ? window : null,
observeElementRect: virtualCore.observeWindowRect,
observeElementOffset: virtualCore.observeWindowOffset,
scrollToFn: virtualCore.windowScroll,
initialOffset: () => typeof document !== "undefined" ? window.scrollY : 0,
...options
});
}
exports.useVirtualizer = useVirtualizer;
exports.useWindowVirtualizer = useWindowVirtualizer;
Object.keys(virtualCore).forEach((k) => {
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
enumerable: true,
get: () => virtualCore[k]
});
});
//# sourceMappingURL=index.cjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.cjs","sources":["../../src/index.tsx"],"sourcesContent":["import * as React from 'react'\nimport { flushSync } from 'react-dom'\nimport {\n Virtualizer,\n elementScroll,\n observeElementOffset,\n observeElementRect,\n observeWindowOffset,\n observeWindowRect,\n windowScroll,\n} from '@tanstack/virtual-core'\nimport type { PartialKeys, VirtualizerOptions } from '@tanstack/virtual-core'\n\nexport * from '@tanstack/virtual-core'\n\nconst useIsomorphicLayoutEffect =\n typeof document !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\nexport type ReactVirtualizerOptions<\n TScrollElement extends Element | Window,\n TItemElement extends Element,\n> = VirtualizerOptions<TScrollElement, TItemElement> & {\n useFlushSync?: boolean\n}\n\nfunction useVirtualizerBase<\n TScrollElement extends Element | Window,\n TItemElement extends Element,\n>({\n useFlushSync = true,\n ...options\n}: ReactVirtualizerOptions<TScrollElement, TItemElement>): Virtualizer<\n TScrollElement,\n TItemElement\n> {\n const rerender = React.useReducer(() => ({}), {})[1]\n\n const resolvedOptions: VirtualizerOptions<TScrollElement, TItemElement> = {\n ...options,\n onChange: (instance, sync) => {\n if (useFlushSync && sync) {\n flushSync(rerender)\n } else {\n rerender()\n }\n options.onChange?.(instance, sync)\n },\n }\n\n const [instance] = React.useState(\n () => new Virtualizer<TScrollElement, TItemElement>(resolvedOptions),\n )\n\n instance.setOptions(resolvedOptions)\n\n useIsomorphicLayoutEffect(() => {\n return instance._didMount()\n }, [])\n\n useIsomorphicLayoutEffect(() => {\n return instance._willUpdate()\n })\n\n return instance\n}\n\nexport function useVirtualizer<\n TScrollElement extends Element,\n TItemElement extends Element,\n>(\n options: PartialKeys<\n ReactVirtualizerOptions<TScrollElement, TItemElement>,\n 'observeElementRect' | 'observeElementOffset' | 'scrollToFn'\n >,\n): Virtualizer<TScrollElement, TItemElement> {\n return useVirtualizerBase<TScrollElement, TItemElement>({\n observeElementRect: observeElementRect,\n observeElementOffset: observeElementOffset,\n scrollToFn: elementScroll,\n ...options,\n })\n}\n\nexport function useWindowVirtualizer<TItemElement extends Element>(\n options: PartialKeys<\n ReactVirtualizerOptions<Window, TItemElement>,\n | 'getScrollElement'\n | 'observeElementRect'\n | 'observeElementOffset'\n | 'scrollToFn'\n >,\n): Virtualizer<Window, TItemElement> {\n return useVirtualizerBase<Window, TItemElement>({\n getScrollElement: () => (typeof document !== 'undefined' ? window : null),\n observeElementRect: observeWindowRect,\n observeElementOffset: observeWindowOffset,\n scrollToFn: windowScroll,\n initialOffset: () => (typeof document !== 'undefined' ? window.scrollY : 0),\n ...options,\n })\n}\n"],"names":["React","instance","flushSync","Virtualizer","observeElementRect","observeElementOffset","elementScroll","observeWindowRect","observeWindowOffset","windowScroll"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAeA,MAAM,4BACJ,OAAO,aAAa,cAAcA,iBAAM,kBAAkBA,iBAAM;AASlE,SAAS,mBAGP;AAAA,EACA,eAAe;AAAA,EACf,GAAG;AACL,GAGE;AACA,QAAM,WAAWA,iBAAM,WAAW,OAAO,CAAA,IAAK,CAAA,CAAE,EAAE,CAAC;AAEnD,QAAM,kBAAoE;AAAA,IACxE,GAAG;AAAA,IACH,UAAU,CAACC,WAAU,SAAS;;AAC5B,UAAI,gBAAgB,MAAM;AACxBC,iBAAAA,UAAU,QAAQ;AAAA,MACpB,OAAO;AACL,iBAAA;AAAA,MACF;AACA,oBAAQ,aAAR,iCAAmBD,WAAU;AAAA,IAC/B;AAAA,EAAA;AAGF,QAAM,CAAC,QAAQ,IAAID,iBAAM;AAAA,IACvB,MAAM,IAAIG,YAAAA,YAA0C,eAAe;AAAA,EAAA;AAGrE,WAAS,WAAW,eAAe;AAEnC,4BAA0B,MAAM;AAC9B,WAAO,SAAS,UAAA;AAAA,EAClB,GAAG,CAAA,CAAE;AAEL,4BAA0B,MAAM;AAC9B,WAAO,SAAS,YAAA;AAAA,EAClB,CAAC;AAED,SAAO;AACT;AAEO,SAAS,eAId,SAI2C;AAC3C,SAAO,mBAAiD;AAAA,IAAA,oBACtDC,YAAAA;AAAAA,IAAA,sBACAC,YAAAA;AAAAA,IACA,YAAYC,YAAAA;AAAAA,IACZ,GAAG;AAAA,EAAA,CACJ;AACH;AAEO,SAAS,qBACd,SAOmC;AACnC,SAAO,mBAAyC;AAAA,IAC9C,kBAAkB,MAAO,OAAO,aAAa,cAAc,SAAS;AAAA,IACpE,oBAAoBC,YAAAA;AAAAA,IACpB,sBAAsBC,YAAAA;AAAAA,IACtB,YAAYC,YAAAA;AAAAA,IACZ,eAAe,MAAO,OAAO,aAAa,cAAc,OAAO,UAAU;AAAA,IACzE,GAAG;AAAA,EAAA,CACJ;AACH;;;;;;;;;"}

View File

@@ -0,0 +1,7 @@
import { Virtualizer, PartialKeys, VirtualizerOptions } from '@tanstack/virtual-core';
export * from '@tanstack/virtual-core';
export type ReactVirtualizerOptions<TScrollElement extends Element | Window, TItemElement extends Element> = VirtualizerOptions<TScrollElement, TItemElement> & {
useFlushSync?: boolean;
};
export declare function useVirtualizer<TScrollElement extends Element, TItemElement extends Element>(options: PartialKeys<ReactVirtualizerOptions<TScrollElement, TItemElement>, 'observeElementRect' | 'observeElementOffset' | 'scrollToFn'>): Virtualizer<TScrollElement, TItemElement>;
export declare function useWindowVirtualizer<TItemElement extends Element>(options: PartialKeys<ReactVirtualizerOptions<Window, TItemElement>, 'getScrollElement' | 'observeElementRect' | 'observeElementOffset' | 'scrollToFn'>): Virtualizer<Window, TItemElement>;

View File

@@ -0,0 +1,7 @@
import { Virtualizer, PartialKeys, VirtualizerOptions } from '@tanstack/virtual-core';
export * from '@tanstack/virtual-core';
export type ReactVirtualizerOptions<TScrollElement extends Element | Window, TItemElement extends Element> = VirtualizerOptions<TScrollElement, TItemElement> & {
useFlushSync?: boolean;
};
export declare function useVirtualizer<TScrollElement extends Element, TItemElement extends Element>(options: PartialKeys<ReactVirtualizerOptions<TScrollElement, TItemElement>, 'observeElementRect' | 'observeElementOffset' | 'scrollToFn'>): Virtualizer<TScrollElement, TItemElement>;
export declare function useWindowVirtualizer<TItemElement extends Element>(options: PartialKeys<ReactVirtualizerOptions<Window, TItemElement>, 'getScrollElement' | 'observeElementRect' | 'observeElementOffset' | 'scrollToFn'>): Virtualizer<Window, TItemElement>;

View File

@@ -0,0 +1,57 @@
import * as React from "react";
import { flushSync } from "react-dom";
import { Virtualizer, elementScroll, observeElementOffset, observeElementRect, windowScroll, observeWindowOffset, observeWindowRect } from "@tanstack/virtual-core";
export * from "@tanstack/virtual-core";
const useIsomorphicLayoutEffect = typeof document !== "undefined" ? React.useLayoutEffect : React.useEffect;
function useVirtualizerBase({
useFlushSync = true,
...options
}) {
const rerender = React.useReducer(() => ({}), {})[1];
const resolvedOptions = {
...options,
onChange: (instance2, sync) => {
var _a;
if (useFlushSync && sync) {
flushSync(rerender);
} else {
rerender();
}
(_a = options.onChange) == null ? void 0 : _a.call(options, instance2, sync);
}
};
const [instance] = React.useState(
() => new Virtualizer(resolvedOptions)
);
instance.setOptions(resolvedOptions);
useIsomorphicLayoutEffect(() => {
return instance._didMount();
}, []);
useIsomorphicLayoutEffect(() => {
return instance._willUpdate();
});
return instance;
}
function useVirtualizer(options) {
return useVirtualizerBase({
observeElementRect,
observeElementOffset,
scrollToFn: elementScroll,
...options
});
}
function useWindowVirtualizer(options) {
return useVirtualizerBase({
getScrollElement: () => typeof document !== "undefined" ? window : null,
observeElementRect: observeWindowRect,
observeElementOffset: observeWindowOffset,
scrollToFn: windowScroll,
initialOffset: () => typeof document !== "undefined" ? window.scrollY : 0,
...options
});
}
export {
useVirtualizer,
useWindowVirtualizer
};
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sources":["../../src/index.tsx"],"sourcesContent":["import * as React from 'react'\nimport { flushSync } from 'react-dom'\nimport {\n Virtualizer,\n elementScroll,\n observeElementOffset,\n observeElementRect,\n observeWindowOffset,\n observeWindowRect,\n windowScroll,\n} from '@tanstack/virtual-core'\nimport type { PartialKeys, VirtualizerOptions } from '@tanstack/virtual-core'\n\nexport * from '@tanstack/virtual-core'\n\nconst useIsomorphicLayoutEffect =\n typeof document !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\nexport type ReactVirtualizerOptions<\n TScrollElement extends Element | Window,\n TItemElement extends Element,\n> = VirtualizerOptions<TScrollElement, TItemElement> & {\n useFlushSync?: boolean\n}\n\nfunction useVirtualizerBase<\n TScrollElement extends Element | Window,\n TItemElement extends Element,\n>({\n useFlushSync = true,\n ...options\n}: ReactVirtualizerOptions<TScrollElement, TItemElement>): Virtualizer<\n TScrollElement,\n TItemElement\n> {\n const rerender = React.useReducer(() => ({}), {})[1]\n\n const resolvedOptions: VirtualizerOptions<TScrollElement, TItemElement> = {\n ...options,\n onChange: (instance, sync) => {\n if (useFlushSync && sync) {\n flushSync(rerender)\n } else {\n rerender()\n }\n options.onChange?.(instance, sync)\n },\n }\n\n const [instance] = React.useState(\n () => new Virtualizer<TScrollElement, TItemElement>(resolvedOptions),\n )\n\n instance.setOptions(resolvedOptions)\n\n useIsomorphicLayoutEffect(() => {\n return instance._didMount()\n }, [])\n\n useIsomorphicLayoutEffect(() => {\n return instance._willUpdate()\n })\n\n return instance\n}\n\nexport function useVirtualizer<\n TScrollElement extends Element,\n TItemElement extends Element,\n>(\n options: PartialKeys<\n ReactVirtualizerOptions<TScrollElement, TItemElement>,\n 'observeElementRect' | 'observeElementOffset' | 'scrollToFn'\n >,\n): Virtualizer<TScrollElement, TItemElement> {\n return useVirtualizerBase<TScrollElement, TItemElement>({\n observeElementRect: observeElementRect,\n observeElementOffset: observeElementOffset,\n scrollToFn: elementScroll,\n ...options,\n })\n}\n\nexport function useWindowVirtualizer<TItemElement extends Element>(\n options: PartialKeys<\n ReactVirtualizerOptions<Window, TItemElement>,\n | 'getScrollElement'\n | 'observeElementRect'\n | 'observeElementOffset'\n | 'scrollToFn'\n >,\n): Virtualizer<Window, TItemElement> {\n return useVirtualizerBase<Window, TItemElement>({\n getScrollElement: () => (typeof document !== 'undefined' ? window : null),\n observeElementRect: observeWindowRect,\n observeElementOffset: observeWindowOffset,\n scrollToFn: windowScroll,\n initialOffset: () => (typeof document !== 'undefined' ? window.scrollY : 0),\n ...options,\n })\n}\n"],"names":["instance"],"mappings":";;;;AAeA,MAAM,4BACJ,OAAO,aAAa,cAAc,MAAM,kBAAkB,MAAM;AASlE,SAAS,mBAGP;AAAA,EACA,eAAe;AAAA,EACf,GAAG;AACL,GAGE;AACA,QAAM,WAAW,MAAM,WAAW,OAAO,CAAA,IAAK,CAAA,CAAE,EAAE,CAAC;AAEnD,QAAM,kBAAoE;AAAA,IACxE,GAAG;AAAA,IACH,UAAU,CAACA,WAAU,SAAS;;AAC5B,UAAI,gBAAgB,MAAM;AACxB,kBAAU,QAAQ;AAAA,MACpB,OAAO;AACL,iBAAA;AAAA,MACF;AACA,oBAAQ,aAAR,iCAAmBA,WAAU;AAAA,IAC/B;AAAA,EAAA;AAGF,QAAM,CAAC,QAAQ,IAAI,MAAM;AAAA,IACvB,MAAM,IAAI,YAA0C,eAAe;AAAA,EAAA;AAGrE,WAAS,WAAW,eAAe;AAEnC,4BAA0B,MAAM;AAC9B,WAAO,SAAS,UAAA;AAAA,EAClB,GAAG,CAAA,CAAE;AAEL,4BAA0B,MAAM;AAC9B,WAAO,SAAS,YAAA;AAAA,EAClB,CAAC;AAED,SAAO;AACT;AAEO,SAAS,eAId,SAI2C;AAC3C,SAAO,mBAAiD;AAAA,IACtD;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,GAAG;AAAA,EAAA,CACJ;AACH;AAEO,SAAS,qBACd,SAOmC;AACnC,SAAO,mBAAyC;AAAA,IAC9C,kBAAkB,MAAO,OAAO,aAAa,cAAc,SAAS;AAAA,IACpE,oBAAoB;AAAA,IACpB,sBAAsB;AAAA,IACtB,YAAY;AAAA,IACZ,eAAe,MAAO,OAAO,aAAa,cAAc,OAAO,UAAU;AAAA,IACzE,GAAG;AAAA,EAAA,CACJ;AACH;"}

View File

@@ -0,0 +1,73 @@
{
"name": "@tanstack/react-virtual",
"version": "3.13.23",
"description": "Headless UI for virtualizing scrollable elements in React",
"author": "Tanner Linsley",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/TanStack/virtual.git",
"directory": "packages/react-virtual"
},
"homepage": "https://tanstack.com/virtual",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"keywords": [
"react",
"vue",
"solid",
"virtual",
"virtual-core",
"datagrid"
],
"type": "module",
"types": "dist/esm/index.d.ts",
"main": "dist/cjs/index.cjs",
"module": "dist/esm/index.js",
"exports": {
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/cjs/index.d.cts",
"default": "./dist/cjs/index.cjs"
}
},
"./package.json": "./package.json"
},
"sideEffects": false,
"files": [
"dist",
"src"
],
"dependencies": {
"@tanstack/virtual-core": "3.13.23"
},
"devDependencies": {
"@testing-library/react": "^16.3.0",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@vitejs/plugin-react": "^4.5.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"resize-observer-polyfill": "^1.5.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"scripts": {
"clean": "premove ./dist ./coverage",
"test:eslint": "eslint ./src",
"test:types": "tsc",
"test:lib": "vitest",
"test:lib:dev": "pnpm run test:lib --watch",
"test:build": "publint --strict",
"build": "vite build",
"test:e2e": "../../node_modules/.bin/playwright test"
}
}

View File

@@ -0,0 +1,101 @@
import * as React from 'react'
import { flushSync } from 'react-dom'
import {
Virtualizer,
elementScroll,
observeElementOffset,
observeElementRect,
observeWindowOffset,
observeWindowRect,
windowScroll,
} from '@tanstack/virtual-core'
import type { PartialKeys, VirtualizerOptions } from '@tanstack/virtual-core'
export * from '@tanstack/virtual-core'
const useIsomorphicLayoutEffect =
typeof document !== 'undefined' ? React.useLayoutEffect : React.useEffect
export type ReactVirtualizerOptions<
TScrollElement extends Element | Window,
TItemElement extends Element,
> = VirtualizerOptions<TScrollElement, TItemElement> & {
useFlushSync?: boolean
}
function useVirtualizerBase<
TScrollElement extends Element | Window,
TItemElement extends Element,
>({
useFlushSync = true,
...options
}: ReactVirtualizerOptions<TScrollElement, TItemElement>): Virtualizer<
TScrollElement,
TItemElement
> {
const rerender = React.useReducer(() => ({}), {})[1]
const resolvedOptions: VirtualizerOptions<TScrollElement, TItemElement> = {
...options,
onChange: (instance, sync) => {
if (useFlushSync && sync) {
flushSync(rerender)
} else {
rerender()
}
options.onChange?.(instance, sync)
},
}
const [instance] = React.useState(
() => new Virtualizer<TScrollElement, TItemElement>(resolvedOptions),
)
instance.setOptions(resolvedOptions)
useIsomorphicLayoutEffect(() => {
return instance._didMount()
}, [])
useIsomorphicLayoutEffect(() => {
return instance._willUpdate()
})
return instance
}
export function useVirtualizer<
TScrollElement extends Element,
TItemElement extends Element,
>(
options: PartialKeys<
ReactVirtualizerOptions<TScrollElement, TItemElement>,
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
>,
): Virtualizer<TScrollElement, TItemElement> {
return useVirtualizerBase<TScrollElement, TItemElement>({
observeElementRect: observeElementRect,
observeElementOffset: observeElementOffset,
scrollToFn: elementScroll,
...options,
})
}
export function useWindowVirtualizer<TItemElement extends Element>(
options: PartialKeys<
ReactVirtualizerOptions<Window, TItemElement>,
| 'getScrollElement'
| 'observeElementRect'
| 'observeElementOffset'
| 'scrollToFn'
>,
): Virtualizer<Window, TItemElement> {
return useVirtualizerBase<Window, TItemElement>({
getScrollElement: () => (typeof document !== 'undefined' ? window : null),
observeElementRect: observeWindowRect,
observeElementOffset: observeWindowOffset,
scrollToFn: windowScroll,
initialOffset: () => (typeof document !== 'undefined' ? window.scrollY : 0),
...options,
})
}

21
frontend/node_modules/@tanstack/virtual-core/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
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.

View File

@@ -0,0 +1,935 @@
"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const utils = require("./utils.cjs");
const getRect = (element) => {
const { offsetWidth, offsetHeight } = element;
return { width: offsetWidth, height: offsetHeight };
};
const defaultKeyExtractor = (index) => index;
const defaultRangeExtractor = (range) => {
const start = Math.max(range.startIndex - range.overscan, 0);
const end = Math.min(range.endIndex + range.overscan, range.count - 1);
const arr = [];
for (let i = start; i <= end; i++) {
arr.push(i);
}
return arr;
};
const observeElementRect = (instance, cb) => {
const element = instance.scrollElement;
if (!element) {
return;
}
const targetWindow = instance.targetWindow;
if (!targetWindow) {
return;
}
const handler = (rect) => {
const { width, height } = rect;
cb({ width: Math.round(width), height: Math.round(height) });
};
handler(getRect(element));
if (!targetWindow.ResizeObserver) {
return () => {
};
}
const observer = new targetWindow.ResizeObserver((entries) => {
const run = () => {
const entry = entries[0];
if (entry == null ? void 0 : entry.borderBoxSize) {
const box = entry.borderBoxSize[0];
if (box) {
handler({ width: box.inlineSize, height: box.blockSize });
return;
}
}
handler(getRect(element));
};
instance.options.useAnimationFrameWithResizeObserver ? requestAnimationFrame(run) : run();
});
observer.observe(element, { box: "border-box" });
return () => {
observer.unobserve(element);
};
};
const addEventListenerOptions = {
passive: true
};
const observeWindowRect = (instance, cb) => {
const element = instance.scrollElement;
if (!element) {
return;
}
const handler = () => {
cb({ width: element.innerWidth, height: element.innerHeight });
};
handler();
element.addEventListener("resize", handler, addEventListenerOptions);
return () => {
element.removeEventListener("resize", handler);
};
};
const supportsScrollend = typeof window == "undefined" ? true : "onscrollend" in window;
const observeElementOffset = (instance, cb) => {
const element = instance.scrollElement;
if (!element) {
return;
}
const targetWindow = instance.targetWindow;
if (!targetWindow) {
return;
}
let offset = 0;
const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : utils.debounce(
targetWindow,
() => {
cb(offset, false);
},
instance.options.isScrollingResetDelay
);
const createHandler = (isScrolling) => () => {
const { horizontal, isRtl } = instance.options;
offset = horizontal ? element["scrollLeft"] * (isRtl && -1 || 1) : element["scrollTop"];
fallback();
cb(offset, isScrolling);
};
const handler = createHandler(true);
const endHandler = createHandler(false);
element.addEventListener("scroll", handler, addEventListenerOptions);
const registerScrollendEvent = instance.options.useScrollendEvent && supportsScrollend;
if (registerScrollendEvent) {
element.addEventListener("scrollend", endHandler, addEventListenerOptions);
}
return () => {
element.removeEventListener("scroll", handler);
if (registerScrollendEvent) {
element.removeEventListener("scrollend", endHandler);
}
};
};
const observeWindowOffset = (instance, cb) => {
const element = instance.scrollElement;
if (!element) {
return;
}
const targetWindow = instance.targetWindow;
if (!targetWindow) {
return;
}
let offset = 0;
const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : utils.debounce(
targetWindow,
() => {
cb(offset, false);
},
instance.options.isScrollingResetDelay
);
const createHandler = (isScrolling) => () => {
offset = element[instance.options.horizontal ? "scrollX" : "scrollY"];
fallback();
cb(offset, isScrolling);
};
const handler = createHandler(true);
const endHandler = createHandler(false);
element.addEventListener("scroll", handler, addEventListenerOptions);
const registerScrollendEvent = instance.options.useScrollendEvent && supportsScrollend;
if (registerScrollendEvent) {
element.addEventListener("scrollend", endHandler, addEventListenerOptions);
}
return () => {
element.removeEventListener("scroll", handler);
if (registerScrollendEvent) {
element.removeEventListener("scrollend", endHandler);
}
};
};
const measureElement = (element, entry, instance) => {
if (entry == null ? void 0 : entry.borderBoxSize) {
const box = entry.borderBoxSize[0];
if (box) {
const size = Math.round(
box[instance.options.horizontal ? "inlineSize" : "blockSize"]
);
return size;
}
}
return element[instance.options.horizontal ? "offsetWidth" : "offsetHeight"];
};
const windowScroll = (offset, {
adjustments = 0,
behavior
}, instance) => {
var _a, _b;
const toOffset = offset + adjustments;
(_b = (_a = instance.scrollElement) == null ? void 0 : _a.scrollTo) == null ? void 0 : _b.call(_a, {
[instance.options.horizontal ? "left" : "top"]: toOffset,
behavior
});
};
const elementScroll = (offset, {
adjustments = 0,
behavior
}, instance) => {
var _a, _b;
const toOffset = offset + adjustments;
(_b = (_a = instance.scrollElement) == null ? void 0 : _a.scrollTo) == null ? void 0 : _b.call(_a, {
[instance.options.horizontal ? "left" : "top"]: toOffset,
behavior
});
};
class Virtualizer {
constructor(opts) {
this.unsubs = [];
this.scrollElement = null;
this.targetWindow = null;
this.isScrolling = false;
this.scrollState = null;
this.measurementsCache = [];
this.itemSizeCache = /* @__PURE__ */ new Map();
this.laneAssignments = /* @__PURE__ */ new Map();
this.pendingMeasuredCacheIndexes = [];
this.prevLanes = void 0;
this.lanesChangedFlag = false;
this.lanesSettling = false;
this.scrollRect = null;
this.scrollOffset = null;
this.scrollDirection = null;
this.scrollAdjustments = 0;
this.elementsCache = /* @__PURE__ */ new Map();
this.now = () => {
var _a, _b, _c;
return ((_c = (_b = (_a = this.targetWindow) == null ? void 0 : _a.performance) == null ? void 0 : _b.now) == null ? void 0 : _c.call(_b)) ?? Date.now();
};
this.observer = /* @__PURE__ */ (() => {
let _ro = null;
const get = () => {
if (_ro) {
return _ro;
}
if (!this.targetWindow || !this.targetWindow.ResizeObserver) {
return null;
}
return _ro = new this.targetWindow.ResizeObserver((entries) => {
entries.forEach((entry) => {
const run = () => {
const node = entry.target;
const index = this.indexFromElement(node);
if (!node.isConnected) {
this.observer.unobserve(node);
return;
}
if (this.shouldMeasureDuringScroll(index)) {
this.resizeItem(
index,
this.options.measureElement(node, entry, this)
);
}
};
this.options.useAnimationFrameWithResizeObserver ? requestAnimationFrame(run) : run();
});
});
};
return {
disconnect: () => {
var _a;
(_a = get()) == null ? void 0 : _a.disconnect();
_ro = null;
},
observe: (target) => {
var _a;
return (_a = get()) == null ? void 0 : _a.observe(target, { box: "border-box" });
},
unobserve: (target) => {
var _a;
return (_a = get()) == null ? void 0 : _a.unobserve(target);
}
};
})();
this.range = null;
this.setOptions = (opts2) => {
Object.entries(opts2).forEach(([key, value]) => {
if (typeof value === "undefined") delete opts2[key];
});
this.options = {
debug: false,
initialOffset: 0,
overscan: 1,
paddingStart: 0,
paddingEnd: 0,
scrollPaddingStart: 0,
scrollPaddingEnd: 0,
horizontal: false,
getItemKey: defaultKeyExtractor,
rangeExtractor: defaultRangeExtractor,
onChange: () => {
},
measureElement,
initialRect: { width: 0, height: 0 },
scrollMargin: 0,
gap: 0,
indexAttribute: "data-index",
initialMeasurementsCache: [],
lanes: 1,
isScrollingResetDelay: 150,
enabled: true,
isRtl: false,
useScrollendEvent: false,
useAnimationFrameWithResizeObserver: false,
...opts2
};
};
this.notify = (sync) => {
var _a, _b;
(_b = (_a = this.options).onChange) == null ? void 0 : _b.call(_a, this, sync);
};
this.maybeNotify = utils.memo(
() => {
this.calculateRange();
return [
this.isScrolling,
this.range ? this.range.startIndex : null,
this.range ? this.range.endIndex : null
];
},
(isScrolling) => {
this.notify(isScrolling);
},
{
key: process.env.NODE_ENV !== "production" && "maybeNotify",
debug: () => this.options.debug,
initialDeps: [
this.isScrolling,
this.range ? this.range.startIndex : null,
this.range ? this.range.endIndex : null
]
}
);
this.cleanup = () => {
this.unsubs.filter(Boolean).forEach((d) => d());
this.unsubs = [];
this.observer.disconnect();
if (this.rafId != null && this.targetWindow) {
this.targetWindow.cancelAnimationFrame(this.rafId);
this.rafId = null;
}
this.scrollState = null;
this.scrollElement = null;
this.targetWindow = null;
};
this._didMount = () => {
return () => {
this.cleanup();
};
};
this._willUpdate = () => {
var _a;
const scrollElement = this.options.enabled ? this.options.getScrollElement() : null;
if (this.scrollElement !== scrollElement) {
this.cleanup();
if (!scrollElement) {
this.maybeNotify();
return;
}
this.scrollElement = scrollElement;
if (this.scrollElement && "ownerDocument" in this.scrollElement) {
this.targetWindow = this.scrollElement.ownerDocument.defaultView;
} else {
this.targetWindow = ((_a = this.scrollElement) == null ? void 0 : _a.window) ?? null;
}
this.elementsCache.forEach((cached) => {
this.observer.observe(cached);
});
this.unsubs.push(
this.options.observeElementRect(this, (rect) => {
this.scrollRect = rect;
this.maybeNotify();
})
);
this.unsubs.push(
this.options.observeElementOffset(this, (offset, isScrolling) => {
this.scrollAdjustments = 0;
this.scrollDirection = isScrolling ? this.getScrollOffset() < offset ? "forward" : "backward" : null;
this.scrollOffset = offset;
this.isScrolling = isScrolling;
if (this.scrollState) {
this.scheduleScrollReconcile();
}
this.maybeNotify();
})
);
this._scrollToOffset(this.getScrollOffset(), {
adjustments: void 0,
behavior: void 0
});
}
};
this.rafId = null;
this.getSize = () => {
if (!this.options.enabled) {
this.scrollRect = null;
return 0;
}
this.scrollRect = this.scrollRect ?? this.options.initialRect;
return this.scrollRect[this.options.horizontal ? "width" : "height"];
};
this.getScrollOffset = () => {
if (!this.options.enabled) {
this.scrollOffset = null;
return 0;
}
this.scrollOffset = this.scrollOffset ?? (typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset);
return this.scrollOffset;
};
this.getFurthestMeasurement = (measurements, index) => {
const furthestMeasurementsFound = /* @__PURE__ */ new Map();
const furthestMeasurements = /* @__PURE__ */ new Map();
for (let m = index - 1; m >= 0; m--) {
const measurement = measurements[m];
if (furthestMeasurementsFound.has(measurement.lane)) {
continue;
}
const previousFurthestMeasurement = furthestMeasurements.get(
measurement.lane
);
if (previousFurthestMeasurement == null || measurement.end > previousFurthestMeasurement.end) {
furthestMeasurements.set(measurement.lane, measurement);
} else if (measurement.end < previousFurthestMeasurement.end) {
furthestMeasurementsFound.set(measurement.lane, true);
}
if (furthestMeasurementsFound.size === this.options.lanes) {
break;
}
}
return furthestMeasurements.size === this.options.lanes ? Array.from(furthestMeasurements.values()).sort((a, b) => {
if (a.end === b.end) {
return a.index - b.index;
}
return a.end - b.end;
})[0] : void 0;
};
this.getMeasurementOptions = utils.memo(
() => [
this.options.count,
this.options.paddingStart,
this.options.scrollMargin,
this.options.getItemKey,
this.options.enabled,
this.options.lanes
],
(count, paddingStart, scrollMargin, getItemKey, enabled, lanes) => {
const lanesChanged = this.prevLanes !== void 0 && this.prevLanes !== lanes;
if (lanesChanged) {
this.lanesChangedFlag = true;
}
this.prevLanes = lanes;
this.pendingMeasuredCacheIndexes = [];
return {
count,
paddingStart,
scrollMargin,
getItemKey,
enabled,
lanes
};
},
{
key: false
}
);
this.getMeasurements = utils.memo(
() => [this.getMeasurementOptions(), this.itemSizeCache],
({ count, paddingStart, scrollMargin, getItemKey, enabled, lanes }, itemSizeCache) => {
if (!enabled) {
this.measurementsCache = [];
this.itemSizeCache.clear();
this.laneAssignments.clear();
return [];
}
if (this.laneAssignments.size > count) {
for (const index of this.laneAssignments.keys()) {
if (index >= count) {
this.laneAssignments.delete(index);
}
}
}
if (this.lanesChangedFlag) {
this.lanesChangedFlag = false;
this.lanesSettling = true;
this.measurementsCache = [];
this.itemSizeCache.clear();
this.laneAssignments.clear();
this.pendingMeasuredCacheIndexes = [];
}
if (this.measurementsCache.length === 0 && !this.lanesSettling) {
this.measurementsCache = this.options.initialMeasurementsCache;
this.measurementsCache.forEach((item) => {
this.itemSizeCache.set(item.key, item.size);
});
}
const min = this.lanesSettling ? 0 : this.pendingMeasuredCacheIndexes.length > 0 ? Math.min(...this.pendingMeasuredCacheIndexes) : 0;
this.pendingMeasuredCacheIndexes = [];
if (this.lanesSettling && this.measurementsCache.length === count) {
this.lanesSettling = false;
}
const measurements = this.measurementsCache.slice(0, min);
const laneLastIndex = new Array(lanes).fill(
void 0
);
for (let m = 0; m < min; m++) {
const item = measurements[m];
if (item) {
laneLastIndex[item.lane] = m;
}
}
for (let i = min; i < count; i++) {
const key = getItemKey(i);
const cachedLane = this.laneAssignments.get(i);
let lane;
let start;
if (cachedLane !== void 0 && this.options.lanes > 1) {
lane = cachedLane;
const prevIndex = laneLastIndex[lane];
const prevInLane = prevIndex !== void 0 ? measurements[prevIndex] : void 0;
start = prevInLane ? prevInLane.end + this.options.gap : paddingStart + scrollMargin;
} else {
const furthestMeasurement = this.options.lanes === 1 ? measurements[i - 1] : this.getFurthestMeasurement(measurements, i);
start = furthestMeasurement ? furthestMeasurement.end + this.options.gap : paddingStart + scrollMargin;
lane = furthestMeasurement ? furthestMeasurement.lane : i % this.options.lanes;
if (this.options.lanes > 1) {
this.laneAssignments.set(i, lane);
}
}
const measuredSize = itemSizeCache.get(key);
const size = typeof measuredSize === "number" ? measuredSize : this.options.estimateSize(i);
const end = start + size;
measurements[i] = {
index: i,
start,
size,
end,
key,
lane
};
laneLastIndex[lane] = i;
}
this.measurementsCache = measurements;
return measurements;
},
{
key: process.env.NODE_ENV !== "production" && "getMeasurements",
debug: () => this.options.debug
}
);
this.calculateRange = utils.memo(
() => [
this.getMeasurements(),
this.getSize(),
this.getScrollOffset(),
this.options.lanes
],
(measurements, outerSize, scrollOffset, lanes) => {
return this.range = measurements.length > 0 && outerSize > 0 ? calculateRange({
measurements,
outerSize,
scrollOffset,
lanes
}) : null;
},
{
key: process.env.NODE_ENV !== "production" && "calculateRange",
debug: () => this.options.debug
}
);
this.getVirtualIndexes = utils.memo(
() => {
let startIndex = null;
let endIndex = null;
const range = this.calculateRange();
if (range) {
startIndex = range.startIndex;
endIndex = range.endIndex;
}
this.maybeNotify.updateDeps([this.isScrolling, startIndex, endIndex]);
return [
this.options.rangeExtractor,
this.options.overscan,
this.options.count,
startIndex,
endIndex
];
},
(rangeExtractor, overscan, count, startIndex, endIndex) => {
return startIndex === null || endIndex === null ? [] : rangeExtractor({
startIndex,
endIndex,
overscan,
count
});
},
{
key: process.env.NODE_ENV !== "production" && "getVirtualIndexes",
debug: () => this.options.debug
}
);
this.indexFromElement = (node) => {
const attributeName = this.options.indexAttribute;
const indexStr = node.getAttribute(attributeName);
if (!indexStr) {
console.warn(
`Missing attribute name '${attributeName}={index}' on measured element.`
);
return -1;
}
return parseInt(indexStr, 10);
};
this.shouldMeasureDuringScroll = (index) => {
var _a;
if (!this.scrollState || this.scrollState.behavior !== "smooth") {
return true;
}
const scrollIndex = this.scrollState.index ?? ((_a = this.getVirtualItemForOffset(this.scrollState.lastTargetOffset)) == null ? void 0 : _a.index);
if (scrollIndex !== void 0 && this.range) {
const bufferSize = Math.max(
this.options.overscan,
Math.ceil((this.range.endIndex - this.range.startIndex) / 2)
);
const minIndex = Math.max(0, scrollIndex - bufferSize);
const maxIndex = Math.min(
this.options.count - 1,
scrollIndex + bufferSize
);
return index >= minIndex && index <= maxIndex;
}
return true;
};
this.measureElement = (node) => {
if (!node) {
this.elementsCache.forEach((cached, key2) => {
if (!cached.isConnected) {
this.observer.unobserve(cached);
this.elementsCache.delete(key2);
}
});
return;
}
const index = this.indexFromElement(node);
const key = this.options.getItemKey(index);
const prevNode = this.elementsCache.get(key);
if (prevNode !== node) {
if (prevNode) {
this.observer.unobserve(prevNode);
}
this.observer.observe(node);
this.elementsCache.set(key, node);
}
if ((!this.isScrolling || this.scrollState) && this.shouldMeasureDuringScroll(index)) {
this.resizeItem(index, this.options.measureElement(node, void 0, this));
}
};
this.resizeItem = (index, size) => {
var _a;
const item = this.measurementsCache[index];
if (!item) return;
const itemSize = this.itemSizeCache.get(item.key) ?? item.size;
const delta = size - itemSize;
if (delta !== 0) {
if (((_a = this.scrollState) == null ? void 0 : _a.behavior) !== "smooth" && (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange(item, delta, this) : item.start < this.getScrollOffset() + this.scrollAdjustments)) {
if (process.env.NODE_ENV !== "production" && this.options.debug) {
console.info("correction", delta);
}
this._scrollToOffset(this.getScrollOffset(), {
adjustments: this.scrollAdjustments += delta,
behavior: void 0
});
}
this.pendingMeasuredCacheIndexes.push(item.index);
this.itemSizeCache = new Map(this.itemSizeCache.set(item.key, size));
this.notify(false);
}
};
this.getVirtualItems = utils.memo(
() => [this.getVirtualIndexes(), this.getMeasurements()],
(indexes, measurements) => {
const virtualItems = [];
for (let k = 0, len = indexes.length; k < len; k++) {
const i = indexes[k];
const measurement = measurements[i];
virtualItems.push(measurement);
}
return virtualItems;
},
{
key: process.env.NODE_ENV !== "production" && "getVirtualItems",
debug: () => this.options.debug
}
);
this.getVirtualItemForOffset = (offset) => {
const measurements = this.getMeasurements();
if (measurements.length === 0) {
return void 0;
}
return utils.notUndefined(
measurements[findNearestBinarySearch(
0,
measurements.length - 1,
(index) => utils.notUndefined(measurements[index]).start,
offset
)]
);
};
this.getMaxScrollOffset = () => {
if (!this.scrollElement) return 0;
if ("scrollHeight" in this.scrollElement) {
return this.options.horizontal ? this.scrollElement.scrollWidth - this.scrollElement.clientWidth : this.scrollElement.scrollHeight - this.scrollElement.clientHeight;
} else {
const doc = this.scrollElement.document.documentElement;
return this.options.horizontal ? doc.scrollWidth - this.scrollElement.innerWidth : doc.scrollHeight - this.scrollElement.innerHeight;
}
};
this.getOffsetForAlignment = (toOffset, align, itemSize = 0) => {
if (!this.scrollElement) return 0;
const size = this.getSize();
const scrollOffset = this.getScrollOffset();
if (align === "auto") {
align = toOffset >= scrollOffset + size ? "end" : "start";
}
if (align === "center") {
toOffset += (itemSize - size) / 2;
} else if (align === "end") {
toOffset -= size;
}
const maxOffset = this.getMaxScrollOffset();
return Math.max(Math.min(maxOffset, toOffset), 0);
};
this.getOffsetForIndex = (index, align = "auto") => {
index = Math.max(0, Math.min(index, this.options.count - 1));
const size = this.getSize();
const scrollOffset = this.getScrollOffset();
const item = this.measurementsCache[index];
if (!item) return;
if (align === "auto") {
if (item.end >= scrollOffset + size - this.options.scrollPaddingEnd) {
align = "end";
} else if (item.start <= scrollOffset + this.options.scrollPaddingStart) {
align = "start";
} else {
return [scrollOffset, align];
}
}
if (align === "end" && index === this.options.count - 1) {
return [this.getMaxScrollOffset(), align];
}
const toOffset = align === "end" ? item.end + this.options.scrollPaddingEnd : item.start - this.options.scrollPaddingStart;
return [
this.getOffsetForAlignment(toOffset, align, item.size),
align
];
};
this.scrollToOffset = (toOffset, { align = "start", behavior = "auto" } = {}) => {
const offset = this.getOffsetForAlignment(toOffset, align);
const now = this.now();
this.scrollState = {
index: null,
align,
behavior,
startedAt: now,
lastTargetOffset: offset,
stableFrames: 0
};
this._scrollToOffset(offset, { adjustments: void 0, behavior });
this.scheduleScrollReconcile();
};
this.scrollToIndex = (index, {
align: initialAlign = "auto",
behavior = "auto"
} = {}) => {
index = Math.max(0, Math.min(index, this.options.count - 1));
const offsetInfo = this.getOffsetForIndex(index, initialAlign);
if (!offsetInfo) {
return;
}
const [offset, align] = offsetInfo;
const now = this.now();
this.scrollState = {
index,
align,
behavior,
startedAt: now,
lastTargetOffset: offset,
stableFrames: 0
};
this._scrollToOffset(offset, { adjustments: void 0, behavior });
this.scheduleScrollReconcile();
};
this.scrollBy = (delta, { behavior = "auto" } = {}) => {
const offset = this.getScrollOffset() + delta;
const now = this.now();
this.scrollState = {
index: null,
align: "start",
behavior,
startedAt: now,
lastTargetOffset: offset,
stableFrames: 0
};
this._scrollToOffset(offset, { adjustments: void 0, behavior });
this.scheduleScrollReconcile();
};
this.getTotalSize = () => {
var _a;
const measurements = this.getMeasurements();
let end;
if (measurements.length === 0) {
end = this.options.paddingStart;
} else if (this.options.lanes === 1) {
end = ((_a = measurements[measurements.length - 1]) == null ? void 0 : _a.end) ?? 0;
} else {
const endByLane = Array(this.options.lanes).fill(null);
let endIndex = measurements.length - 1;
while (endIndex >= 0 && endByLane.some((val) => val === null)) {
const item = measurements[endIndex];
if (endByLane[item.lane] === null) {
endByLane[item.lane] = item.end;
}
endIndex--;
}
end = Math.max(...endByLane.filter((val) => val !== null));
}
return Math.max(
end - this.options.scrollMargin + this.options.paddingEnd,
0
);
};
this._scrollToOffset = (offset, {
adjustments,
behavior
}) => {
this.options.scrollToFn(offset, { behavior, adjustments }, this);
};
this.measure = () => {
this.itemSizeCache = /* @__PURE__ */ new Map();
this.laneAssignments = /* @__PURE__ */ new Map();
this.notify(false);
};
this.setOptions(opts);
}
scheduleScrollReconcile() {
if (!this.targetWindow) {
this.scrollState = null;
return;
}
if (this.rafId != null) return;
this.rafId = this.targetWindow.requestAnimationFrame(() => {
this.rafId = null;
this.reconcileScroll();
});
}
reconcileScroll() {
if (!this.scrollState) return;
const el = this.scrollElement;
if (!el) return;
const MAX_RECONCILE_MS = 5e3;
if (this.now() - this.scrollState.startedAt > MAX_RECONCILE_MS) {
this.scrollState = null;
return;
}
const offsetInfo = this.scrollState.index != null ? this.getOffsetForIndex(this.scrollState.index, this.scrollState.align) : void 0;
const targetOffset = offsetInfo ? offsetInfo[0] : this.scrollState.lastTargetOffset;
const STABLE_FRAMES = 1;
const targetChanged = targetOffset !== this.scrollState.lastTargetOffset;
if (!targetChanged && utils.approxEqual(targetOffset, this.getScrollOffset())) {
this.scrollState.stableFrames++;
if (this.scrollState.stableFrames >= STABLE_FRAMES) {
this.scrollState = null;
return;
}
} else {
this.scrollState.stableFrames = 0;
if (targetChanged) {
this.scrollState.lastTargetOffset = targetOffset;
this.scrollState.behavior = "auto";
this._scrollToOffset(targetOffset, {
adjustments: void 0,
behavior: "auto"
});
}
}
this.scheduleScrollReconcile();
}
}
const findNearestBinarySearch = (low, high, getCurrentValue, value) => {
while (low <= high) {
const middle = (low + high) / 2 | 0;
const currentValue = getCurrentValue(middle);
if (currentValue < value) {
low = middle + 1;
} else if (currentValue > value) {
high = middle - 1;
} else {
return middle;
}
}
if (low > 0) {
return low - 1;
} else {
return 0;
}
};
function calculateRange({
measurements,
outerSize,
scrollOffset,
lanes
}) {
const lastIndex = measurements.length - 1;
const getOffset = (index) => measurements[index].start;
if (measurements.length <= lanes) {
return {
startIndex: 0,
endIndex: lastIndex
};
}
let startIndex = findNearestBinarySearch(
0,
lastIndex,
getOffset,
scrollOffset
);
let endIndex = startIndex;
if (lanes === 1) {
while (endIndex < lastIndex && measurements[endIndex].end < scrollOffset + outerSize) {
endIndex++;
}
} else if (lanes > 1) {
const endPerLane = Array(lanes).fill(0);
while (endIndex < lastIndex && endPerLane.some((pos) => pos < scrollOffset + outerSize)) {
const item = measurements[endIndex];
endPerLane[item.lane] = item.end;
endIndex++;
}
const startPerLane = Array(lanes).fill(scrollOffset + outerSize);
while (startIndex >= 0 && startPerLane.some((pos) => pos >= scrollOffset)) {
const item = measurements[startIndex];
startPerLane[item.lane] = item.start;
startIndex--;
}
startIndex = Math.max(0, startIndex - startIndex % lanes);
endIndex = Math.min(lastIndex, endIndex + (lanes - 1 - endIndex % lanes));
}
return { startIndex, endIndex };
}
exports.approxEqual = utils.approxEqual;
exports.debounce = utils.debounce;
exports.memo = utils.memo;
exports.notUndefined = utils.notUndefined;
exports.Virtualizer = Virtualizer;
exports.defaultKeyExtractor = defaultKeyExtractor;
exports.defaultRangeExtractor = defaultRangeExtractor;
exports.elementScroll = elementScroll;
exports.measureElement = measureElement;
exports.observeElementOffset = observeElementOffset;
exports.observeElementRect = observeElementRect;
exports.observeWindowOffset = observeWindowOffset;
exports.observeWindowRect = observeWindowRect;
exports.windowScroll = windowScroll;
//# sourceMappingURL=index.cjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,155 @@
export * from './utils.cjs';
type ScrollDirection = 'forward' | 'backward';
type ScrollAlignment = 'start' | 'center' | 'end' | 'auto';
type ScrollBehavior = 'auto' | 'smooth' | 'instant';
export interface ScrollToOptions {
align?: ScrollAlignment;
behavior?: ScrollBehavior;
}
type ScrollToOffsetOptions = ScrollToOptions;
type ScrollToIndexOptions = ScrollToOptions;
export interface Range {
startIndex: number;
endIndex: number;
overscan: number;
count: number;
}
type Key = number | string | bigint;
export interface VirtualItem {
key: Key;
index: number;
start: number;
end: number;
size: number;
lane: number;
}
export interface Rect {
width: number;
height: number;
}
export declare const defaultKeyExtractor: (index: number) => number;
export declare const defaultRangeExtractor: (range: Range) => number[];
export declare const observeElementRect: <T extends Element>(instance: Virtualizer<T, any>, cb: (rect: Rect) => void) => (() => void) | undefined;
export declare const observeWindowRect: (instance: Virtualizer<Window, any>, cb: (rect: Rect) => void) => (() => void) | undefined;
type ObserveOffsetCallBack = (offset: number, isScrolling: boolean) => void;
export declare const observeElementOffset: <T extends Element>(instance: Virtualizer<T, any>, cb: ObserveOffsetCallBack) => (() => void) | undefined;
export declare const observeWindowOffset: (instance: Virtualizer<Window, any>, cb: ObserveOffsetCallBack) => (() => void) | undefined;
export declare const measureElement: <TItemElement extends Element>(element: TItemElement, entry: ResizeObserverEntry | undefined, instance: Virtualizer<any, TItemElement>) => number;
export declare const windowScroll: <T extends Window>(offset: number, { adjustments, behavior, }: {
adjustments?: number;
behavior?: ScrollBehavior;
}, instance: Virtualizer<T, any>) => void;
export declare const elementScroll: <T extends Element>(offset: number, { adjustments, behavior, }: {
adjustments?: number;
behavior?: ScrollBehavior;
}, instance: Virtualizer<T, any>) => void;
export interface VirtualizerOptions<TScrollElement extends Element | Window, TItemElement extends Element> {
count: number;
getScrollElement: () => TScrollElement | null;
estimateSize: (index: number) => number;
scrollToFn: (offset: number, options: {
adjustments?: number;
behavior?: ScrollBehavior;
}, instance: Virtualizer<TScrollElement, TItemElement>) => void;
observeElementRect: (instance: Virtualizer<TScrollElement, TItemElement>, cb: (rect: Rect) => void) => void | (() => void);
observeElementOffset: (instance: Virtualizer<TScrollElement, TItemElement>, cb: ObserveOffsetCallBack) => void | (() => void);
debug?: boolean;
initialRect?: Rect;
onChange?: (instance: Virtualizer<TScrollElement, TItemElement>, sync: boolean) => void;
measureElement?: (element: TItemElement, entry: ResizeObserverEntry | undefined, instance: Virtualizer<TScrollElement, TItemElement>) => number;
overscan?: number;
horizontal?: boolean;
paddingStart?: number;
paddingEnd?: number;
scrollPaddingStart?: number;
scrollPaddingEnd?: number;
initialOffset?: number | (() => number);
getItemKey?: (index: number) => Key;
rangeExtractor?: (range: Range) => Array<number>;
scrollMargin?: number;
gap?: number;
indexAttribute?: string;
initialMeasurementsCache?: Array<VirtualItem>;
lanes?: number;
isScrollingResetDelay?: number;
useScrollendEvent?: boolean;
enabled?: boolean;
isRtl?: boolean;
useAnimationFrameWithResizeObserver?: boolean;
}
export declare class Virtualizer<TScrollElement extends Element | Window, TItemElement extends Element> {
private unsubs;
options: Required<VirtualizerOptions<TScrollElement, TItemElement>>;
scrollElement: TScrollElement | null;
targetWindow: (Window & typeof globalThis) | null;
isScrolling: boolean;
private scrollState;
measurementsCache: Array<VirtualItem>;
private itemSizeCache;
private laneAssignments;
private pendingMeasuredCacheIndexes;
private prevLanes;
private lanesChangedFlag;
private lanesSettling;
scrollRect: Rect | null;
scrollOffset: number | null;
scrollDirection: ScrollDirection | null;
private scrollAdjustments;
shouldAdjustScrollPositionOnItemSizeChange: undefined | ((item: VirtualItem, delta: number, instance: Virtualizer<TScrollElement, TItemElement>) => boolean);
elementsCache: Map<Key, TItemElement>;
private now;
private observer;
range: {
startIndex: number;
endIndex: number;
} | null;
constructor(opts: VirtualizerOptions<TScrollElement, TItemElement>);
setOptions: (opts: VirtualizerOptions<TScrollElement, TItemElement>) => void;
private notify;
private maybeNotify;
private cleanup;
_didMount: () => () => void;
_willUpdate: () => void;
private rafId;
private scheduleScrollReconcile;
private reconcileScroll;
private getSize;
private getScrollOffset;
private getFurthestMeasurement;
private getMeasurementOptions;
private getMeasurements;
calculateRange: {
(): {
startIndex: number;
endIndex: number;
} | null;
updateDeps(newDeps: [VirtualItem[], number, number, number]): void;
};
getVirtualIndexes: {
(): number[];
updateDeps(newDeps: [(range: Range) => number[], number, number, number | null, number | null]): void;
};
indexFromElement: (node: TItemElement) => number;
/**
* Determines if an item at the given index should be measured during smooth scroll.
* During smooth scroll, only items within a buffer range around the target are measured
* to prevent items far from the target from pushing it away.
*/
private shouldMeasureDuringScroll;
measureElement: (node: TItemElement | null) => void;
resizeItem: (index: number, size: number) => void;
getVirtualItems: {
(): VirtualItem[];
updateDeps(newDeps: [number[], VirtualItem[]]): void;
};
getVirtualItemForOffset: (offset: number) => VirtualItem | undefined;
private getMaxScrollOffset;
getOffsetForAlignment: (toOffset: number, align: ScrollAlignment, itemSize?: number) => number;
getOffsetForIndex: (index: number, align?: ScrollAlignment) => readonly [number, "auto"] | readonly [number, "start" | "center" | "end"] | undefined;
scrollToOffset: (toOffset: number, { align, behavior }?: ScrollToOffsetOptions) => void;
scrollToIndex: (index: number, { align: initialAlign, behavior, }?: ScrollToIndexOptions) => void;
scrollBy: (delta: number, { behavior }?: ScrollToOffsetOptions) => void;
getTotalSize: () => number;
private _scrollToOffset;
measure: () => void;
}

View File

@@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
function memo(getDeps, fn, opts) {
let deps = opts.initialDeps ?? [];
let result;
let isInitial = true;
function memoizedFunction() {
var _a, _b, _c;
let depTime;
if (opts.key && ((_a = opts.debug) == null ? void 0 : _a.call(opts))) depTime = Date.now();
const newDeps = getDeps();
const depsChanged = newDeps.length !== deps.length || newDeps.some((dep, index) => deps[index] !== dep);
if (!depsChanged) {
return result;
}
deps = newDeps;
let resultTime;
if (opts.key && ((_b = opts.debug) == null ? void 0 : _b.call(opts))) resultTime = Date.now();
result = fn(...newDeps);
if (opts.key && ((_c = opts.debug) == null ? void 0 : _c.call(opts))) {
const depEndTime = Math.round((Date.now() - depTime) * 100) / 100;
const resultEndTime = Math.round((Date.now() - resultTime) * 100) / 100;
const resultFpsPercentage = resultEndTime / 16;
const pad = (str, num) => {
str = String(str);
while (str.length < num) {
str = " " + str;
}
return str;
};
console.info(
`%c⏱ ${pad(resultEndTime, 5)} /${pad(depEndTime, 5)} ms`,
`
font-size: .6rem;
font-weight: bold;
color: hsl(${Math.max(
0,
Math.min(120 - 120 * resultFpsPercentage, 120)
)}deg 100% 31%);`,
opts == null ? void 0 : opts.key
);
}
if ((opts == null ? void 0 : opts.onChange) && !(isInitial && opts.skipInitialOnChange)) {
opts.onChange(result);
}
isInitial = false;
return result;
}
memoizedFunction.updateDeps = (newDeps) => {
deps = newDeps;
};
return memoizedFunction;
}
function notUndefined(value, msg) {
if (value === void 0) {
throw new Error(`Unexpected undefined${msg ? `: ${msg}` : ""}`);
} else {
return value;
}
}
const approxEqual = (a, b) => Math.abs(a - b) < 1.01;
const debounce = (targetWindow, fn, ms) => {
let timeoutId;
return function(...args) {
targetWindow.clearTimeout(timeoutId);
timeoutId = targetWindow.setTimeout(() => fn.apply(this, args), ms);
};
};
exports.approxEqual = approxEqual;
exports.debounce = debounce;
exports.memo = memo;
exports.notUndefined = notUndefined;
//# sourceMappingURL=utils.cjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"utils.cjs","sources":["../../src/utils.ts"],"sourcesContent":["export type NoInfer<A extends any> = [A][A extends any ? 0 : never]\n\nexport type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>\n\nexport function memo<TDeps extends ReadonlyArray<any>, TResult>(\n getDeps: () => [...TDeps],\n fn: (...args: NoInfer<[...TDeps]>) => TResult,\n opts: {\n key: false | string\n debug?: () => boolean\n onChange?: (result: TResult) => void\n initialDeps?: TDeps\n skipInitialOnChange?: boolean\n },\n) {\n let deps = opts.initialDeps ?? []\n let result: TResult | undefined\n let isInitial = true\n\n function memoizedFunction(): TResult {\n let depTime: number\n if (opts.key && opts.debug?.()) depTime = Date.now()\n\n const newDeps = getDeps()\n\n const depsChanged =\n newDeps.length !== deps.length ||\n newDeps.some((dep: any, index: number) => deps[index] !== dep)\n\n if (!depsChanged) {\n return result!\n }\n\n deps = newDeps\n\n let resultTime: number\n if (opts.key && opts.debug?.()) resultTime = Date.now()\n\n result = fn(...newDeps)\n\n if (opts.key && opts.debug?.()) {\n const depEndTime = Math.round((Date.now() - depTime!) * 100) / 100\n const resultEndTime = Math.round((Date.now() - resultTime!) * 100) / 100\n const resultFpsPercentage = resultEndTime / 16\n\n const pad = (str: number | string, num: number) => {\n str = String(str)\n while (str.length < num) {\n str = ' ' + str\n }\n return str\n }\n\n console.info(\n `%c⏱ ${pad(resultEndTime, 5)} /${pad(depEndTime, 5)} ms`,\n `\n font-size: .6rem;\n font-weight: bold;\n color: hsl(${Math.max(\n 0,\n Math.min(120 - 120 * resultFpsPercentage, 120),\n )}deg 100% 31%);`,\n opts?.key,\n )\n }\n\n if (opts?.onChange && !(isInitial && opts.skipInitialOnChange)) {\n opts.onChange(result)\n }\n\n isInitial = false\n\n return result\n }\n\n // Attach updateDeps to the function itself\n memoizedFunction.updateDeps = (newDeps: [...TDeps]) => {\n deps = newDeps\n }\n\n return memoizedFunction\n}\n\nexport function notUndefined<T>(value: T | undefined, msg?: string): T {\n if (value === undefined) {\n throw new Error(`Unexpected undefined${msg ? `: ${msg}` : ''}`)\n } else {\n return value\n }\n}\n\nexport const approxEqual = (a: number, b: number) => Math.abs(a - b) < 1.01\n\nexport const debounce = (\n targetWindow: Window & typeof globalThis,\n fn: Function,\n ms: number,\n) => {\n let timeoutId: number\n return function (this: any, ...args: Array<any>) {\n targetWindow.clearTimeout(timeoutId)\n timeoutId = targetWindow.setTimeout(() => fn.apply(this, args), ms)\n }\n}\n"],"names":[],"mappings":";;AAIO,SAAS,KACd,SACA,IACA,MAOA;AACA,MAAI,OAAO,KAAK,eAAe,CAAA;AAC/B,MAAI;AACJ,MAAI,YAAY;AAEhB,WAAS,mBAA4B;;AACnC,QAAI;AACJ,QAAI,KAAK,SAAO,UAAK,UAAL,+BAAgB,WAAU,KAAK,IAAA;AAE/C,UAAM,UAAU,QAAA;AAEhB,UAAM,cACJ,QAAQ,WAAW,KAAK,UACxB,QAAQ,KAAK,CAAC,KAAU,UAAkB,KAAK,KAAK,MAAM,GAAG;AAE/D,QAAI,CAAC,aAAa;AAChB,aAAO;AAAA,IACT;AAEA,WAAO;AAEP,QAAI;AACJ,QAAI,KAAK,SAAO,UAAK,UAAL,+BAAgB,cAAa,KAAK,IAAA;AAElD,aAAS,GAAG,GAAG,OAAO;AAEtB,QAAI,KAAK,SAAO,UAAK,UAAL,gCAAgB;AAC9B,YAAM,aAAa,KAAK,OAAO,KAAK,QAAQ,WAAY,GAAG,IAAI;AAC/D,YAAM,gBAAgB,KAAK,OAAO,KAAK,QAAQ,cAAe,GAAG,IAAI;AACrE,YAAM,sBAAsB,gBAAgB;AAE5C,YAAM,MAAM,CAAC,KAAsB,QAAgB;AACjD,cAAM,OAAO,GAAG;AAChB,eAAO,IAAI,SAAS,KAAK;AACvB,gBAAM,MAAM;AAAA,QACd;AACA,eAAO;AAAA,MACT;AAEA,cAAQ;AAAA,QACN,OAAO,IAAI,eAAe,CAAC,CAAC,KAAK,IAAI,YAAY,CAAC,CAAC;AAAA,QACnD;AAAA;AAAA;AAAA,yBAGiB,KAAK;AAAA,UAChB;AAAA,UACA,KAAK,IAAI,MAAM,MAAM,qBAAqB,GAAG;AAAA,QAAA,CAC9C;AAAA,QACL,6BAAM;AAAA,MAAA;AAAA,IAEV;AAEA,SAAI,6BAAM,aAAY,EAAE,aAAa,KAAK,sBAAsB;AAC9D,WAAK,SAAS,MAAM;AAAA,IACtB;AAEA,gBAAY;AAEZ,WAAO;AAAA,EACT;AAGA,mBAAiB,aAAa,CAAC,YAAwB;AACrD,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,aAAgB,OAAsB,KAAiB;AACrE,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI,MAAM,uBAAuB,MAAM,KAAK,GAAG,KAAK,EAAE,EAAE;AAAA,EAChE,OAAO;AACL,WAAO;AAAA,EACT;AACF;AAEO,MAAM,cAAc,CAAC,GAAW,MAAc,KAAK,IAAI,IAAI,CAAC,IAAI;AAEhE,MAAM,WAAW,CACtB,cACA,IACA,OACG;AACH,MAAI;AACJ,SAAO,YAAwB,MAAkB;AAC/C,iBAAa,aAAa,SAAS;AACnC,gBAAY,aAAa,WAAW,MAAM,GAAG,MAAM,MAAM,IAAI,GAAG,EAAE;AAAA,EACpE;AACF;;;;;"}

View File

@@ -0,0 +1,15 @@
export type NoInfer<A extends any> = [A][A extends any ? 0 : never];
export type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export declare function memo<TDeps extends ReadonlyArray<any>, TResult>(getDeps: () => [...TDeps], fn: (...args: NoInfer<[...TDeps]>) => TResult, opts: {
key: false | string;
debug?: () => boolean;
onChange?: (result: TResult) => void;
initialDeps?: TDeps;
skipInitialOnChange?: boolean;
}): {
(): TResult;
updateDeps(newDeps: [...TDeps]): void;
};
export declare function notUndefined<T>(value: T | undefined, msg?: string): T;
export declare const approxEqual: (a: number, b: number) => boolean;
export declare const debounce: (targetWindow: Window & typeof globalThis, fn: Function, ms: number) => (this: any, ...args: Array<any>) => void;

View File

@@ -0,0 +1,155 @@
export * from './utils.js';
type ScrollDirection = 'forward' | 'backward';
type ScrollAlignment = 'start' | 'center' | 'end' | 'auto';
type ScrollBehavior = 'auto' | 'smooth' | 'instant';
export interface ScrollToOptions {
align?: ScrollAlignment;
behavior?: ScrollBehavior;
}
type ScrollToOffsetOptions = ScrollToOptions;
type ScrollToIndexOptions = ScrollToOptions;
export interface Range {
startIndex: number;
endIndex: number;
overscan: number;
count: number;
}
type Key = number | string | bigint;
export interface VirtualItem {
key: Key;
index: number;
start: number;
end: number;
size: number;
lane: number;
}
export interface Rect {
width: number;
height: number;
}
export declare const defaultKeyExtractor: (index: number) => number;
export declare const defaultRangeExtractor: (range: Range) => number[];
export declare const observeElementRect: <T extends Element>(instance: Virtualizer<T, any>, cb: (rect: Rect) => void) => (() => void) | undefined;
export declare const observeWindowRect: (instance: Virtualizer<Window, any>, cb: (rect: Rect) => void) => (() => void) | undefined;
type ObserveOffsetCallBack = (offset: number, isScrolling: boolean) => void;
export declare const observeElementOffset: <T extends Element>(instance: Virtualizer<T, any>, cb: ObserveOffsetCallBack) => (() => void) | undefined;
export declare const observeWindowOffset: (instance: Virtualizer<Window, any>, cb: ObserveOffsetCallBack) => (() => void) | undefined;
export declare const measureElement: <TItemElement extends Element>(element: TItemElement, entry: ResizeObserverEntry | undefined, instance: Virtualizer<any, TItemElement>) => number;
export declare const windowScroll: <T extends Window>(offset: number, { adjustments, behavior, }: {
adjustments?: number;
behavior?: ScrollBehavior;
}, instance: Virtualizer<T, any>) => void;
export declare const elementScroll: <T extends Element>(offset: number, { adjustments, behavior, }: {
adjustments?: number;
behavior?: ScrollBehavior;
}, instance: Virtualizer<T, any>) => void;
export interface VirtualizerOptions<TScrollElement extends Element | Window, TItemElement extends Element> {
count: number;
getScrollElement: () => TScrollElement | null;
estimateSize: (index: number) => number;
scrollToFn: (offset: number, options: {
adjustments?: number;
behavior?: ScrollBehavior;
}, instance: Virtualizer<TScrollElement, TItemElement>) => void;
observeElementRect: (instance: Virtualizer<TScrollElement, TItemElement>, cb: (rect: Rect) => void) => void | (() => void);
observeElementOffset: (instance: Virtualizer<TScrollElement, TItemElement>, cb: ObserveOffsetCallBack) => void | (() => void);
debug?: boolean;
initialRect?: Rect;
onChange?: (instance: Virtualizer<TScrollElement, TItemElement>, sync: boolean) => void;
measureElement?: (element: TItemElement, entry: ResizeObserverEntry | undefined, instance: Virtualizer<TScrollElement, TItemElement>) => number;
overscan?: number;
horizontal?: boolean;
paddingStart?: number;
paddingEnd?: number;
scrollPaddingStart?: number;
scrollPaddingEnd?: number;
initialOffset?: number | (() => number);
getItemKey?: (index: number) => Key;
rangeExtractor?: (range: Range) => Array<number>;
scrollMargin?: number;
gap?: number;
indexAttribute?: string;
initialMeasurementsCache?: Array<VirtualItem>;
lanes?: number;
isScrollingResetDelay?: number;
useScrollendEvent?: boolean;
enabled?: boolean;
isRtl?: boolean;
useAnimationFrameWithResizeObserver?: boolean;
}
export declare class Virtualizer<TScrollElement extends Element | Window, TItemElement extends Element> {
private unsubs;
options: Required<VirtualizerOptions<TScrollElement, TItemElement>>;
scrollElement: TScrollElement | null;
targetWindow: (Window & typeof globalThis) | null;
isScrolling: boolean;
private scrollState;
measurementsCache: Array<VirtualItem>;
private itemSizeCache;
private laneAssignments;
private pendingMeasuredCacheIndexes;
private prevLanes;
private lanesChangedFlag;
private lanesSettling;
scrollRect: Rect | null;
scrollOffset: number | null;
scrollDirection: ScrollDirection | null;
private scrollAdjustments;
shouldAdjustScrollPositionOnItemSizeChange: undefined | ((item: VirtualItem, delta: number, instance: Virtualizer<TScrollElement, TItemElement>) => boolean);
elementsCache: Map<Key, TItemElement>;
private now;
private observer;
range: {
startIndex: number;
endIndex: number;
} | null;
constructor(opts: VirtualizerOptions<TScrollElement, TItemElement>);
setOptions: (opts: VirtualizerOptions<TScrollElement, TItemElement>) => void;
private notify;
private maybeNotify;
private cleanup;
_didMount: () => () => void;
_willUpdate: () => void;
private rafId;
private scheduleScrollReconcile;
private reconcileScroll;
private getSize;
private getScrollOffset;
private getFurthestMeasurement;
private getMeasurementOptions;
private getMeasurements;
calculateRange: {
(): {
startIndex: number;
endIndex: number;
} | null;
updateDeps(newDeps: [VirtualItem[], number, number, number]): void;
};
getVirtualIndexes: {
(): number[];
updateDeps(newDeps: [(range: Range) => number[], number, number, number | null, number | null]): void;
};
indexFromElement: (node: TItemElement) => number;
/**
* Determines if an item at the given index should be measured during smooth scroll.
* During smooth scroll, only items within a buffer range around the target are measured
* to prevent items far from the target from pushing it away.
*/
private shouldMeasureDuringScroll;
measureElement: (node: TItemElement | null) => void;
resizeItem: (index: number, size: number) => void;
getVirtualItems: {
(): VirtualItem[];
updateDeps(newDeps: [number[], VirtualItem[]]): void;
};
getVirtualItemForOffset: (offset: number) => VirtualItem | undefined;
private getMaxScrollOffset;
getOffsetForAlignment: (toOffset: number, align: ScrollAlignment, itemSize?: number) => number;
getOffsetForIndex: (index: number, align?: ScrollAlignment) => readonly [number, "auto"] | readonly [number, "start" | "center" | "end"] | undefined;
scrollToOffset: (toOffset: number, { align, behavior }?: ScrollToOffsetOptions) => void;
scrollToIndex: (index: number, { align: initialAlign, behavior, }?: ScrollToIndexOptions) => void;
scrollBy: (delta: number, { behavior }?: ScrollToOffsetOptions) => void;
getTotalSize: () => number;
private _scrollToOffset;
measure: () => void;
}

View File

@@ -0,0 +1,935 @@
import { debounce, memo, notUndefined, approxEqual } from "./utils.js";
const getRect = (element) => {
const { offsetWidth, offsetHeight } = element;
return { width: offsetWidth, height: offsetHeight };
};
const defaultKeyExtractor = (index) => index;
const defaultRangeExtractor = (range) => {
const start = Math.max(range.startIndex - range.overscan, 0);
const end = Math.min(range.endIndex + range.overscan, range.count - 1);
const arr = [];
for (let i = start; i <= end; i++) {
arr.push(i);
}
return arr;
};
const observeElementRect = (instance, cb) => {
const element = instance.scrollElement;
if (!element) {
return;
}
const targetWindow = instance.targetWindow;
if (!targetWindow) {
return;
}
const handler = (rect) => {
const { width, height } = rect;
cb({ width: Math.round(width), height: Math.round(height) });
};
handler(getRect(element));
if (!targetWindow.ResizeObserver) {
return () => {
};
}
const observer = new targetWindow.ResizeObserver((entries) => {
const run = () => {
const entry = entries[0];
if (entry == null ? void 0 : entry.borderBoxSize) {
const box = entry.borderBoxSize[0];
if (box) {
handler({ width: box.inlineSize, height: box.blockSize });
return;
}
}
handler(getRect(element));
};
instance.options.useAnimationFrameWithResizeObserver ? requestAnimationFrame(run) : run();
});
observer.observe(element, { box: "border-box" });
return () => {
observer.unobserve(element);
};
};
const addEventListenerOptions = {
passive: true
};
const observeWindowRect = (instance, cb) => {
const element = instance.scrollElement;
if (!element) {
return;
}
const handler = () => {
cb({ width: element.innerWidth, height: element.innerHeight });
};
handler();
element.addEventListener("resize", handler, addEventListenerOptions);
return () => {
element.removeEventListener("resize", handler);
};
};
const supportsScrollend = typeof window == "undefined" ? true : "onscrollend" in window;
const observeElementOffset = (instance, cb) => {
const element = instance.scrollElement;
if (!element) {
return;
}
const targetWindow = instance.targetWindow;
if (!targetWindow) {
return;
}
let offset = 0;
const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : debounce(
targetWindow,
() => {
cb(offset, false);
},
instance.options.isScrollingResetDelay
);
const createHandler = (isScrolling) => () => {
const { horizontal, isRtl } = instance.options;
offset = horizontal ? element["scrollLeft"] * (isRtl && -1 || 1) : element["scrollTop"];
fallback();
cb(offset, isScrolling);
};
const handler = createHandler(true);
const endHandler = createHandler(false);
element.addEventListener("scroll", handler, addEventListenerOptions);
const registerScrollendEvent = instance.options.useScrollendEvent && supportsScrollend;
if (registerScrollendEvent) {
element.addEventListener("scrollend", endHandler, addEventListenerOptions);
}
return () => {
element.removeEventListener("scroll", handler);
if (registerScrollendEvent) {
element.removeEventListener("scrollend", endHandler);
}
};
};
const observeWindowOffset = (instance, cb) => {
const element = instance.scrollElement;
if (!element) {
return;
}
const targetWindow = instance.targetWindow;
if (!targetWindow) {
return;
}
let offset = 0;
const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : debounce(
targetWindow,
() => {
cb(offset, false);
},
instance.options.isScrollingResetDelay
);
const createHandler = (isScrolling) => () => {
offset = element[instance.options.horizontal ? "scrollX" : "scrollY"];
fallback();
cb(offset, isScrolling);
};
const handler = createHandler(true);
const endHandler = createHandler(false);
element.addEventListener("scroll", handler, addEventListenerOptions);
const registerScrollendEvent = instance.options.useScrollendEvent && supportsScrollend;
if (registerScrollendEvent) {
element.addEventListener("scrollend", endHandler, addEventListenerOptions);
}
return () => {
element.removeEventListener("scroll", handler);
if (registerScrollendEvent) {
element.removeEventListener("scrollend", endHandler);
}
};
};
const measureElement = (element, entry, instance) => {
if (entry == null ? void 0 : entry.borderBoxSize) {
const box = entry.borderBoxSize[0];
if (box) {
const size = Math.round(
box[instance.options.horizontal ? "inlineSize" : "blockSize"]
);
return size;
}
}
return element[instance.options.horizontal ? "offsetWidth" : "offsetHeight"];
};
const windowScroll = (offset, {
adjustments = 0,
behavior
}, instance) => {
var _a, _b;
const toOffset = offset + adjustments;
(_b = (_a = instance.scrollElement) == null ? void 0 : _a.scrollTo) == null ? void 0 : _b.call(_a, {
[instance.options.horizontal ? "left" : "top"]: toOffset,
behavior
});
};
const elementScroll = (offset, {
adjustments = 0,
behavior
}, instance) => {
var _a, _b;
const toOffset = offset + adjustments;
(_b = (_a = instance.scrollElement) == null ? void 0 : _a.scrollTo) == null ? void 0 : _b.call(_a, {
[instance.options.horizontal ? "left" : "top"]: toOffset,
behavior
});
};
class Virtualizer {
constructor(opts) {
this.unsubs = [];
this.scrollElement = null;
this.targetWindow = null;
this.isScrolling = false;
this.scrollState = null;
this.measurementsCache = [];
this.itemSizeCache = /* @__PURE__ */ new Map();
this.laneAssignments = /* @__PURE__ */ new Map();
this.pendingMeasuredCacheIndexes = [];
this.prevLanes = void 0;
this.lanesChangedFlag = false;
this.lanesSettling = false;
this.scrollRect = null;
this.scrollOffset = null;
this.scrollDirection = null;
this.scrollAdjustments = 0;
this.elementsCache = /* @__PURE__ */ new Map();
this.now = () => {
var _a, _b, _c;
return ((_c = (_b = (_a = this.targetWindow) == null ? void 0 : _a.performance) == null ? void 0 : _b.now) == null ? void 0 : _c.call(_b)) ?? Date.now();
};
this.observer = /* @__PURE__ */ (() => {
let _ro = null;
const get = () => {
if (_ro) {
return _ro;
}
if (!this.targetWindow || !this.targetWindow.ResizeObserver) {
return null;
}
return _ro = new this.targetWindow.ResizeObserver((entries) => {
entries.forEach((entry) => {
const run = () => {
const node = entry.target;
const index = this.indexFromElement(node);
if (!node.isConnected) {
this.observer.unobserve(node);
return;
}
if (this.shouldMeasureDuringScroll(index)) {
this.resizeItem(
index,
this.options.measureElement(node, entry, this)
);
}
};
this.options.useAnimationFrameWithResizeObserver ? requestAnimationFrame(run) : run();
});
});
};
return {
disconnect: () => {
var _a;
(_a = get()) == null ? void 0 : _a.disconnect();
_ro = null;
},
observe: (target) => {
var _a;
return (_a = get()) == null ? void 0 : _a.observe(target, { box: "border-box" });
},
unobserve: (target) => {
var _a;
return (_a = get()) == null ? void 0 : _a.unobserve(target);
}
};
})();
this.range = null;
this.setOptions = (opts2) => {
Object.entries(opts2).forEach(([key, value]) => {
if (typeof value === "undefined") delete opts2[key];
});
this.options = {
debug: false,
initialOffset: 0,
overscan: 1,
paddingStart: 0,
paddingEnd: 0,
scrollPaddingStart: 0,
scrollPaddingEnd: 0,
horizontal: false,
getItemKey: defaultKeyExtractor,
rangeExtractor: defaultRangeExtractor,
onChange: () => {
},
measureElement,
initialRect: { width: 0, height: 0 },
scrollMargin: 0,
gap: 0,
indexAttribute: "data-index",
initialMeasurementsCache: [],
lanes: 1,
isScrollingResetDelay: 150,
enabled: true,
isRtl: false,
useScrollendEvent: false,
useAnimationFrameWithResizeObserver: false,
...opts2
};
};
this.notify = (sync) => {
var _a, _b;
(_b = (_a = this.options).onChange) == null ? void 0 : _b.call(_a, this, sync);
};
this.maybeNotify = memo(
() => {
this.calculateRange();
return [
this.isScrolling,
this.range ? this.range.startIndex : null,
this.range ? this.range.endIndex : null
];
},
(isScrolling) => {
this.notify(isScrolling);
},
{
key: process.env.NODE_ENV !== "production" && "maybeNotify",
debug: () => this.options.debug,
initialDeps: [
this.isScrolling,
this.range ? this.range.startIndex : null,
this.range ? this.range.endIndex : null
]
}
);
this.cleanup = () => {
this.unsubs.filter(Boolean).forEach((d) => d());
this.unsubs = [];
this.observer.disconnect();
if (this.rafId != null && this.targetWindow) {
this.targetWindow.cancelAnimationFrame(this.rafId);
this.rafId = null;
}
this.scrollState = null;
this.scrollElement = null;
this.targetWindow = null;
};
this._didMount = () => {
return () => {
this.cleanup();
};
};
this._willUpdate = () => {
var _a;
const scrollElement = this.options.enabled ? this.options.getScrollElement() : null;
if (this.scrollElement !== scrollElement) {
this.cleanup();
if (!scrollElement) {
this.maybeNotify();
return;
}
this.scrollElement = scrollElement;
if (this.scrollElement && "ownerDocument" in this.scrollElement) {
this.targetWindow = this.scrollElement.ownerDocument.defaultView;
} else {
this.targetWindow = ((_a = this.scrollElement) == null ? void 0 : _a.window) ?? null;
}
this.elementsCache.forEach((cached) => {
this.observer.observe(cached);
});
this.unsubs.push(
this.options.observeElementRect(this, (rect) => {
this.scrollRect = rect;
this.maybeNotify();
})
);
this.unsubs.push(
this.options.observeElementOffset(this, (offset, isScrolling) => {
this.scrollAdjustments = 0;
this.scrollDirection = isScrolling ? this.getScrollOffset() < offset ? "forward" : "backward" : null;
this.scrollOffset = offset;
this.isScrolling = isScrolling;
if (this.scrollState) {
this.scheduleScrollReconcile();
}
this.maybeNotify();
})
);
this._scrollToOffset(this.getScrollOffset(), {
adjustments: void 0,
behavior: void 0
});
}
};
this.rafId = null;
this.getSize = () => {
if (!this.options.enabled) {
this.scrollRect = null;
return 0;
}
this.scrollRect = this.scrollRect ?? this.options.initialRect;
return this.scrollRect[this.options.horizontal ? "width" : "height"];
};
this.getScrollOffset = () => {
if (!this.options.enabled) {
this.scrollOffset = null;
return 0;
}
this.scrollOffset = this.scrollOffset ?? (typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset);
return this.scrollOffset;
};
this.getFurthestMeasurement = (measurements, index) => {
const furthestMeasurementsFound = /* @__PURE__ */ new Map();
const furthestMeasurements = /* @__PURE__ */ new Map();
for (let m = index - 1; m >= 0; m--) {
const measurement = measurements[m];
if (furthestMeasurementsFound.has(measurement.lane)) {
continue;
}
const previousFurthestMeasurement = furthestMeasurements.get(
measurement.lane
);
if (previousFurthestMeasurement == null || measurement.end > previousFurthestMeasurement.end) {
furthestMeasurements.set(measurement.lane, measurement);
} else if (measurement.end < previousFurthestMeasurement.end) {
furthestMeasurementsFound.set(measurement.lane, true);
}
if (furthestMeasurementsFound.size === this.options.lanes) {
break;
}
}
return furthestMeasurements.size === this.options.lanes ? Array.from(furthestMeasurements.values()).sort((a, b) => {
if (a.end === b.end) {
return a.index - b.index;
}
return a.end - b.end;
})[0] : void 0;
};
this.getMeasurementOptions = memo(
() => [
this.options.count,
this.options.paddingStart,
this.options.scrollMargin,
this.options.getItemKey,
this.options.enabled,
this.options.lanes
],
(count, paddingStart, scrollMargin, getItemKey, enabled, lanes) => {
const lanesChanged = this.prevLanes !== void 0 && this.prevLanes !== lanes;
if (lanesChanged) {
this.lanesChangedFlag = true;
}
this.prevLanes = lanes;
this.pendingMeasuredCacheIndexes = [];
return {
count,
paddingStart,
scrollMargin,
getItemKey,
enabled,
lanes
};
},
{
key: false
}
);
this.getMeasurements = memo(
() => [this.getMeasurementOptions(), this.itemSizeCache],
({ count, paddingStart, scrollMargin, getItemKey, enabled, lanes }, itemSizeCache) => {
if (!enabled) {
this.measurementsCache = [];
this.itemSizeCache.clear();
this.laneAssignments.clear();
return [];
}
if (this.laneAssignments.size > count) {
for (const index of this.laneAssignments.keys()) {
if (index >= count) {
this.laneAssignments.delete(index);
}
}
}
if (this.lanesChangedFlag) {
this.lanesChangedFlag = false;
this.lanesSettling = true;
this.measurementsCache = [];
this.itemSizeCache.clear();
this.laneAssignments.clear();
this.pendingMeasuredCacheIndexes = [];
}
if (this.measurementsCache.length === 0 && !this.lanesSettling) {
this.measurementsCache = this.options.initialMeasurementsCache;
this.measurementsCache.forEach((item) => {
this.itemSizeCache.set(item.key, item.size);
});
}
const min = this.lanesSettling ? 0 : this.pendingMeasuredCacheIndexes.length > 0 ? Math.min(...this.pendingMeasuredCacheIndexes) : 0;
this.pendingMeasuredCacheIndexes = [];
if (this.lanesSettling && this.measurementsCache.length === count) {
this.lanesSettling = false;
}
const measurements = this.measurementsCache.slice(0, min);
const laneLastIndex = new Array(lanes).fill(
void 0
);
for (let m = 0; m < min; m++) {
const item = measurements[m];
if (item) {
laneLastIndex[item.lane] = m;
}
}
for (let i = min; i < count; i++) {
const key = getItemKey(i);
const cachedLane = this.laneAssignments.get(i);
let lane;
let start;
if (cachedLane !== void 0 && this.options.lanes > 1) {
lane = cachedLane;
const prevIndex = laneLastIndex[lane];
const prevInLane = prevIndex !== void 0 ? measurements[prevIndex] : void 0;
start = prevInLane ? prevInLane.end + this.options.gap : paddingStart + scrollMargin;
} else {
const furthestMeasurement = this.options.lanes === 1 ? measurements[i - 1] : this.getFurthestMeasurement(measurements, i);
start = furthestMeasurement ? furthestMeasurement.end + this.options.gap : paddingStart + scrollMargin;
lane = furthestMeasurement ? furthestMeasurement.lane : i % this.options.lanes;
if (this.options.lanes > 1) {
this.laneAssignments.set(i, lane);
}
}
const measuredSize = itemSizeCache.get(key);
const size = typeof measuredSize === "number" ? measuredSize : this.options.estimateSize(i);
const end = start + size;
measurements[i] = {
index: i,
start,
size,
end,
key,
lane
};
laneLastIndex[lane] = i;
}
this.measurementsCache = measurements;
return measurements;
},
{
key: process.env.NODE_ENV !== "production" && "getMeasurements",
debug: () => this.options.debug
}
);
this.calculateRange = memo(
() => [
this.getMeasurements(),
this.getSize(),
this.getScrollOffset(),
this.options.lanes
],
(measurements, outerSize, scrollOffset, lanes) => {
return this.range = measurements.length > 0 && outerSize > 0 ? calculateRange({
measurements,
outerSize,
scrollOffset,
lanes
}) : null;
},
{
key: process.env.NODE_ENV !== "production" && "calculateRange",
debug: () => this.options.debug
}
);
this.getVirtualIndexes = memo(
() => {
let startIndex = null;
let endIndex = null;
const range = this.calculateRange();
if (range) {
startIndex = range.startIndex;
endIndex = range.endIndex;
}
this.maybeNotify.updateDeps([this.isScrolling, startIndex, endIndex]);
return [
this.options.rangeExtractor,
this.options.overscan,
this.options.count,
startIndex,
endIndex
];
},
(rangeExtractor, overscan, count, startIndex, endIndex) => {
return startIndex === null || endIndex === null ? [] : rangeExtractor({
startIndex,
endIndex,
overscan,
count
});
},
{
key: process.env.NODE_ENV !== "production" && "getVirtualIndexes",
debug: () => this.options.debug
}
);
this.indexFromElement = (node) => {
const attributeName = this.options.indexAttribute;
const indexStr = node.getAttribute(attributeName);
if (!indexStr) {
console.warn(
`Missing attribute name '${attributeName}={index}' on measured element.`
);
return -1;
}
return parseInt(indexStr, 10);
};
this.shouldMeasureDuringScroll = (index) => {
var _a;
if (!this.scrollState || this.scrollState.behavior !== "smooth") {
return true;
}
const scrollIndex = this.scrollState.index ?? ((_a = this.getVirtualItemForOffset(this.scrollState.lastTargetOffset)) == null ? void 0 : _a.index);
if (scrollIndex !== void 0 && this.range) {
const bufferSize = Math.max(
this.options.overscan,
Math.ceil((this.range.endIndex - this.range.startIndex) / 2)
);
const minIndex = Math.max(0, scrollIndex - bufferSize);
const maxIndex = Math.min(
this.options.count - 1,
scrollIndex + bufferSize
);
return index >= minIndex && index <= maxIndex;
}
return true;
};
this.measureElement = (node) => {
if (!node) {
this.elementsCache.forEach((cached, key2) => {
if (!cached.isConnected) {
this.observer.unobserve(cached);
this.elementsCache.delete(key2);
}
});
return;
}
const index = this.indexFromElement(node);
const key = this.options.getItemKey(index);
const prevNode = this.elementsCache.get(key);
if (prevNode !== node) {
if (prevNode) {
this.observer.unobserve(prevNode);
}
this.observer.observe(node);
this.elementsCache.set(key, node);
}
if ((!this.isScrolling || this.scrollState) && this.shouldMeasureDuringScroll(index)) {
this.resizeItem(index, this.options.measureElement(node, void 0, this));
}
};
this.resizeItem = (index, size) => {
var _a;
const item = this.measurementsCache[index];
if (!item) return;
const itemSize = this.itemSizeCache.get(item.key) ?? item.size;
const delta = size - itemSize;
if (delta !== 0) {
if (((_a = this.scrollState) == null ? void 0 : _a.behavior) !== "smooth" && (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange(item, delta, this) : item.start < this.getScrollOffset() + this.scrollAdjustments)) {
if (process.env.NODE_ENV !== "production" && this.options.debug) {
console.info("correction", delta);
}
this._scrollToOffset(this.getScrollOffset(), {
adjustments: this.scrollAdjustments += delta,
behavior: void 0
});
}
this.pendingMeasuredCacheIndexes.push(item.index);
this.itemSizeCache = new Map(this.itemSizeCache.set(item.key, size));
this.notify(false);
}
};
this.getVirtualItems = memo(
() => [this.getVirtualIndexes(), this.getMeasurements()],
(indexes, measurements) => {
const virtualItems = [];
for (let k = 0, len = indexes.length; k < len; k++) {
const i = indexes[k];
const measurement = measurements[i];
virtualItems.push(measurement);
}
return virtualItems;
},
{
key: process.env.NODE_ENV !== "production" && "getVirtualItems",
debug: () => this.options.debug
}
);
this.getVirtualItemForOffset = (offset) => {
const measurements = this.getMeasurements();
if (measurements.length === 0) {
return void 0;
}
return notUndefined(
measurements[findNearestBinarySearch(
0,
measurements.length - 1,
(index) => notUndefined(measurements[index]).start,
offset
)]
);
};
this.getMaxScrollOffset = () => {
if (!this.scrollElement) return 0;
if ("scrollHeight" in this.scrollElement) {
return this.options.horizontal ? this.scrollElement.scrollWidth - this.scrollElement.clientWidth : this.scrollElement.scrollHeight - this.scrollElement.clientHeight;
} else {
const doc = this.scrollElement.document.documentElement;
return this.options.horizontal ? doc.scrollWidth - this.scrollElement.innerWidth : doc.scrollHeight - this.scrollElement.innerHeight;
}
};
this.getOffsetForAlignment = (toOffset, align, itemSize = 0) => {
if (!this.scrollElement) return 0;
const size = this.getSize();
const scrollOffset = this.getScrollOffset();
if (align === "auto") {
align = toOffset >= scrollOffset + size ? "end" : "start";
}
if (align === "center") {
toOffset += (itemSize - size) / 2;
} else if (align === "end") {
toOffset -= size;
}
const maxOffset = this.getMaxScrollOffset();
return Math.max(Math.min(maxOffset, toOffset), 0);
};
this.getOffsetForIndex = (index, align = "auto") => {
index = Math.max(0, Math.min(index, this.options.count - 1));
const size = this.getSize();
const scrollOffset = this.getScrollOffset();
const item = this.measurementsCache[index];
if (!item) return;
if (align === "auto") {
if (item.end >= scrollOffset + size - this.options.scrollPaddingEnd) {
align = "end";
} else if (item.start <= scrollOffset + this.options.scrollPaddingStart) {
align = "start";
} else {
return [scrollOffset, align];
}
}
if (align === "end" && index === this.options.count - 1) {
return [this.getMaxScrollOffset(), align];
}
const toOffset = align === "end" ? item.end + this.options.scrollPaddingEnd : item.start - this.options.scrollPaddingStart;
return [
this.getOffsetForAlignment(toOffset, align, item.size),
align
];
};
this.scrollToOffset = (toOffset, { align = "start", behavior = "auto" } = {}) => {
const offset = this.getOffsetForAlignment(toOffset, align);
const now = this.now();
this.scrollState = {
index: null,
align,
behavior,
startedAt: now,
lastTargetOffset: offset,
stableFrames: 0
};
this._scrollToOffset(offset, { adjustments: void 0, behavior });
this.scheduleScrollReconcile();
};
this.scrollToIndex = (index, {
align: initialAlign = "auto",
behavior = "auto"
} = {}) => {
index = Math.max(0, Math.min(index, this.options.count - 1));
const offsetInfo = this.getOffsetForIndex(index, initialAlign);
if (!offsetInfo) {
return;
}
const [offset, align] = offsetInfo;
const now = this.now();
this.scrollState = {
index,
align,
behavior,
startedAt: now,
lastTargetOffset: offset,
stableFrames: 0
};
this._scrollToOffset(offset, { adjustments: void 0, behavior });
this.scheduleScrollReconcile();
};
this.scrollBy = (delta, { behavior = "auto" } = {}) => {
const offset = this.getScrollOffset() + delta;
const now = this.now();
this.scrollState = {
index: null,
align: "start",
behavior,
startedAt: now,
lastTargetOffset: offset,
stableFrames: 0
};
this._scrollToOffset(offset, { adjustments: void 0, behavior });
this.scheduleScrollReconcile();
};
this.getTotalSize = () => {
var _a;
const measurements = this.getMeasurements();
let end;
if (measurements.length === 0) {
end = this.options.paddingStart;
} else if (this.options.lanes === 1) {
end = ((_a = measurements[measurements.length - 1]) == null ? void 0 : _a.end) ?? 0;
} else {
const endByLane = Array(this.options.lanes).fill(null);
let endIndex = measurements.length - 1;
while (endIndex >= 0 && endByLane.some((val) => val === null)) {
const item = measurements[endIndex];
if (endByLane[item.lane] === null) {
endByLane[item.lane] = item.end;
}
endIndex--;
}
end = Math.max(...endByLane.filter((val) => val !== null));
}
return Math.max(
end - this.options.scrollMargin + this.options.paddingEnd,
0
);
};
this._scrollToOffset = (offset, {
adjustments,
behavior
}) => {
this.options.scrollToFn(offset, { behavior, adjustments }, this);
};
this.measure = () => {
this.itemSizeCache = /* @__PURE__ */ new Map();
this.laneAssignments = /* @__PURE__ */ new Map();
this.notify(false);
};
this.setOptions(opts);
}
scheduleScrollReconcile() {
if (!this.targetWindow) {
this.scrollState = null;
return;
}
if (this.rafId != null) return;
this.rafId = this.targetWindow.requestAnimationFrame(() => {
this.rafId = null;
this.reconcileScroll();
});
}
reconcileScroll() {
if (!this.scrollState) return;
const el = this.scrollElement;
if (!el) return;
const MAX_RECONCILE_MS = 5e3;
if (this.now() - this.scrollState.startedAt > MAX_RECONCILE_MS) {
this.scrollState = null;
return;
}
const offsetInfo = this.scrollState.index != null ? this.getOffsetForIndex(this.scrollState.index, this.scrollState.align) : void 0;
const targetOffset = offsetInfo ? offsetInfo[0] : this.scrollState.lastTargetOffset;
const STABLE_FRAMES = 1;
const targetChanged = targetOffset !== this.scrollState.lastTargetOffset;
if (!targetChanged && approxEqual(targetOffset, this.getScrollOffset())) {
this.scrollState.stableFrames++;
if (this.scrollState.stableFrames >= STABLE_FRAMES) {
this.scrollState = null;
return;
}
} else {
this.scrollState.stableFrames = 0;
if (targetChanged) {
this.scrollState.lastTargetOffset = targetOffset;
this.scrollState.behavior = "auto";
this._scrollToOffset(targetOffset, {
adjustments: void 0,
behavior: "auto"
});
}
}
this.scheduleScrollReconcile();
}
}
const findNearestBinarySearch = (low, high, getCurrentValue, value) => {
while (low <= high) {
const middle = (low + high) / 2 | 0;
const currentValue = getCurrentValue(middle);
if (currentValue < value) {
low = middle + 1;
} else if (currentValue > value) {
high = middle - 1;
} else {
return middle;
}
}
if (low > 0) {
return low - 1;
} else {
return 0;
}
};
function calculateRange({
measurements,
outerSize,
scrollOffset,
lanes
}) {
const lastIndex = measurements.length - 1;
const getOffset = (index) => measurements[index].start;
if (measurements.length <= lanes) {
return {
startIndex: 0,
endIndex: lastIndex
};
}
let startIndex = findNearestBinarySearch(
0,
lastIndex,
getOffset,
scrollOffset
);
let endIndex = startIndex;
if (lanes === 1) {
while (endIndex < lastIndex && measurements[endIndex].end < scrollOffset + outerSize) {
endIndex++;
}
} else if (lanes > 1) {
const endPerLane = Array(lanes).fill(0);
while (endIndex < lastIndex && endPerLane.some((pos) => pos < scrollOffset + outerSize)) {
const item = measurements[endIndex];
endPerLane[item.lane] = item.end;
endIndex++;
}
const startPerLane = Array(lanes).fill(scrollOffset + outerSize);
while (startIndex >= 0 && startPerLane.some((pos) => pos >= scrollOffset)) {
const item = measurements[startIndex];
startPerLane[item.lane] = item.start;
startIndex--;
}
startIndex = Math.max(0, startIndex - startIndex % lanes);
endIndex = Math.min(lastIndex, endIndex + (lanes - 1 - endIndex % lanes));
}
return { startIndex, endIndex };
}
export {
Virtualizer,
approxEqual,
debounce,
defaultKeyExtractor,
defaultRangeExtractor,
elementScroll,
measureElement,
memo,
notUndefined,
observeElementOffset,
observeElementRect,
observeWindowOffset,
observeWindowRect,
windowScroll
};
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,15 @@
export type NoInfer<A extends any> = [A][A extends any ? 0 : never];
export type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export declare function memo<TDeps extends ReadonlyArray<any>, TResult>(getDeps: () => [...TDeps], fn: (...args: NoInfer<[...TDeps]>) => TResult, opts: {
key: false | string;
debug?: () => boolean;
onChange?: (result: TResult) => void;
initialDeps?: TDeps;
skipInitialOnChange?: boolean;
}): {
(): TResult;
updateDeps(newDeps: [...TDeps]): void;
};
export declare function notUndefined<T>(value: T | undefined, msg?: string): T;
export declare const approxEqual: (a: number, b: number) => boolean;
export declare const debounce: (targetWindow: Window & typeof globalThis, fn: Function, ms: number) => (this: any, ...args: Array<any>) => void;

View File

@@ -0,0 +1,73 @@
function memo(getDeps, fn, opts) {
let deps = opts.initialDeps ?? [];
let result;
let isInitial = true;
function memoizedFunction() {
var _a, _b, _c;
let depTime;
if (opts.key && ((_a = opts.debug) == null ? void 0 : _a.call(opts))) depTime = Date.now();
const newDeps = getDeps();
const depsChanged = newDeps.length !== deps.length || newDeps.some((dep, index) => deps[index] !== dep);
if (!depsChanged) {
return result;
}
deps = newDeps;
let resultTime;
if (opts.key && ((_b = opts.debug) == null ? void 0 : _b.call(opts))) resultTime = Date.now();
result = fn(...newDeps);
if (opts.key && ((_c = opts.debug) == null ? void 0 : _c.call(opts))) {
const depEndTime = Math.round((Date.now() - depTime) * 100) / 100;
const resultEndTime = Math.round((Date.now() - resultTime) * 100) / 100;
const resultFpsPercentage = resultEndTime / 16;
const pad = (str, num) => {
str = String(str);
while (str.length < num) {
str = " " + str;
}
return str;
};
console.info(
`%c⏱ ${pad(resultEndTime, 5)} /${pad(depEndTime, 5)} ms`,
`
font-size: .6rem;
font-weight: bold;
color: hsl(${Math.max(
0,
Math.min(120 - 120 * resultFpsPercentage, 120)
)}deg 100% 31%);`,
opts == null ? void 0 : opts.key
);
}
if ((opts == null ? void 0 : opts.onChange) && !(isInitial && opts.skipInitialOnChange)) {
opts.onChange(result);
}
isInitial = false;
return result;
}
memoizedFunction.updateDeps = (newDeps) => {
deps = newDeps;
};
return memoizedFunction;
}
function notUndefined(value, msg) {
if (value === void 0) {
throw new Error(`Unexpected undefined${msg ? `: ${msg}` : ""}`);
} else {
return value;
}
}
const approxEqual = (a, b) => Math.abs(a - b) < 1.01;
const debounce = (targetWindow, fn, ms) => {
let timeoutId;
return function(...args) {
targetWindow.clearTimeout(timeoutId);
timeoutId = targetWindow.setTimeout(() => fn.apply(this, args), ms);
};
};
export {
approxEqual,
debounce,
memo,
notUndefined
};
//# sourceMappingURL=utils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["export type NoInfer<A extends any> = [A][A extends any ? 0 : never]\n\nexport type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>\n\nexport function memo<TDeps extends ReadonlyArray<any>, TResult>(\n getDeps: () => [...TDeps],\n fn: (...args: NoInfer<[...TDeps]>) => TResult,\n opts: {\n key: false | string\n debug?: () => boolean\n onChange?: (result: TResult) => void\n initialDeps?: TDeps\n skipInitialOnChange?: boolean\n },\n) {\n let deps = opts.initialDeps ?? []\n let result: TResult | undefined\n let isInitial = true\n\n function memoizedFunction(): TResult {\n let depTime: number\n if (opts.key && opts.debug?.()) depTime = Date.now()\n\n const newDeps = getDeps()\n\n const depsChanged =\n newDeps.length !== deps.length ||\n newDeps.some((dep: any, index: number) => deps[index] !== dep)\n\n if (!depsChanged) {\n return result!\n }\n\n deps = newDeps\n\n let resultTime: number\n if (opts.key && opts.debug?.()) resultTime = Date.now()\n\n result = fn(...newDeps)\n\n if (opts.key && opts.debug?.()) {\n const depEndTime = Math.round((Date.now() - depTime!) * 100) / 100\n const resultEndTime = Math.round((Date.now() - resultTime!) * 100) / 100\n const resultFpsPercentage = resultEndTime / 16\n\n const pad = (str: number | string, num: number) => {\n str = String(str)\n while (str.length < num) {\n str = ' ' + str\n }\n return str\n }\n\n console.info(\n `%c⏱ ${pad(resultEndTime, 5)} /${pad(depEndTime, 5)} ms`,\n `\n font-size: .6rem;\n font-weight: bold;\n color: hsl(${Math.max(\n 0,\n Math.min(120 - 120 * resultFpsPercentage, 120),\n )}deg 100% 31%);`,\n opts?.key,\n )\n }\n\n if (opts?.onChange && !(isInitial && opts.skipInitialOnChange)) {\n opts.onChange(result)\n }\n\n isInitial = false\n\n return result\n }\n\n // Attach updateDeps to the function itself\n memoizedFunction.updateDeps = (newDeps: [...TDeps]) => {\n deps = newDeps\n }\n\n return memoizedFunction\n}\n\nexport function notUndefined<T>(value: T | undefined, msg?: string): T {\n if (value === undefined) {\n throw new Error(`Unexpected undefined${msg ? `: ${msg}` : ''}`)\n } else {\n return value\n }\n}\n\nexport const approxEqual = (a: number, b: number) => Math.abs(a - b) < 1.01\n\nexport const debounce = (\n targetWindow: Window & typeof globalThis,\n fn: Function,\n ms: number,\n) => {\n let timeoutId: number\n return function (this: any, ...args: Array<any>) {\n targetWindow.clearTimeout(timeoutId)\n timeoutId = targetWindow.setTimeout(() => fn.apply(this, args), ms)\n }\n}\n"],"names":[],"mappings":"AAIO,SAAS,KACd,SACA,IACA,MAOA;AACA,MAAI,OAAO,KAAK,eAAe,CAAA;AAC/B,MAAI;AACJ,MAAI,YAAY;AAEhB,WAAS,mBAA4B;AAfhC;AAgBH,QAAI;AACJ,QAAI,KAAK,SAAO,UAAK,UAAL,+BAAgB,WAAU,KAAK,IAAA;AAE/C,UAAM,UAAU,QAAA;AAEhB,UAAM,cACJ,QAAQ,WAAW,KAAK,UACxB,QAAQ,KAAK,CAAC,KAAU,UAAkB,KAAK,KAAK,MAAM,GAAG;AAE/D,QAAI,CAAC,aAAa;AAChB,aAAO;AAAA,IACT;AAEA,WAAO;AAEP,QAAI;AACJ,QAAI,KAAK,SAAO,UAAK,UAAL,+BAAgB,cAAa,KAAK,IAAA;AAElD,aAAS,GAAG,GAAG,OAAO;AAEtB,QAAI,KAAK,SAAO,UAAK,UAAL,gCAAgB;AAC9B,YAAM,aAAa,KAAK,OAAO,KAAK,QAAQ,WAAY,GAAG,IAAI;AAC/D,YAAM,gBAAgB,KAAK,OAAO,KAAK,QAAQ,cAAe,GAAG,IAAI;AACrE,YAAM,sBAAsB,gBAAgB;AAE5C,YAAM,MAAM,CAAC,KAAsB,QAAgB;AACjD,cAAM,OAAO,GAAG;AAChB,eAAO,IAAI,SAAS,KAAK;AACvB,gBAAM,MAAM;AAAA,QACd;AACA,eAAO;AAAA,MACT;AAEA,cAAQ;AAAA,QACN,OAAO,IAAI,eAAe,CAAC,CAAC,KAAK,IAAI,YAAY,CAAC,CAAC;AAAA,QACnD;AAAA;AAAA;AAAA,yBAGiB,KAAK;AAAA,UAChB;AAAA,UACA,KAAK,IAAI,MAAM,MAAM,qBAAqB,GAAG;AAAA,QAAA,CAC9C;AAAA,QACL,6BAAM;AAAA,MAAA;AAAA,IAEV;AAEA,SAAI,6BAAM,aAAY,EAAE,aAAa,KAAK,sBAAsB;AAC9D,WAAK,SAAS,MAAM;AAAA,IACtB;AAEA,gBAAY;AAEZ,WAAO;AAAA,EACT;AAGA,mBAAiB,aAAa,CAAC,YAAwB;AACrD,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,aAAgB,OAAsB,KAAiB;AACrE,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI,MAAM,uBAAuB,MAAM,KAAK,GAAG,KAAK,EAAE,EAAE;AAAA,EAChE,OAAO;AACL,WAAO;AAAA,EACT;AACF;AAEO,MAAM,cAAc,CAAC,GAAW,MAAc,KAAK,IAAI,IAAI,CAAC,IAAI;AAEhE,MAAM,WAAW,CACtB,cACA,IACA,OACG;AACH,MAAI;AACJ,SAAO,YAAwB,MAAkB;AAC/C,iBAAa,aAAa,SAAS;AACnC,gBAAY,aAAa,WAAW,MAAM,GAAG,MAAM,MAAM,IAAI,GAAG,EAAE;AAAA,EACpE;AACF;"}

View File

@@ -0,0 +1,56 @@
{
"name": "@tanstack/virtual-core",
"version": "3.13.23",
"description": "Headless UI for virtualizing scrollable elements in TS/JS + Frameworks",
"author": "Tanner Linsley",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/TanStack/virtual.git",
"directory": "packages/virtual-core"
},
"homepage": "https://tanstack.com/virtual",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"keywords": [
"react",
"vue",
"solid",
"virtual",
"virtual-core",
"datagrid"
],
"type": "module",
"types": "dist/esm/index.d.ts",
"main": "dist/cjs/index.cjs",
"module": "dist/esm/index.js",
"exports": {
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/cjs/index.d.cts",
"default": "./dist/cjs/index.cjs"
}
},
"./package.json": "./package.json"
},
"sideEffects": false,
"files": [
"dist",
"src"
],
"scripts": {
"clean": "premove ./dist ./coverage",
"test:eslint": "eslint ./src",
"test:types": "tsc",
"test:lib": "vitest",
"test:lib:dev": "pnpm run test:lib --watch",
"test:build": "publint --strict",
"build": "vite build"
}
}

1397
frontend/node_modules/@tanstack/virtual-core/src/index.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
export type NoInfer<A extends any> = [A][A extends any ? 0 : never]
export type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
export function memo<TDeps extends ReadonlyArray<any>, TResult>(
getDeps: () => [...TDeps],
fn: (...args: NoInfer<[...TDeps]>) => TResult,
opts: {
key: false | string
debug?: () => boolean
onChange?: (result: TResult) => void
initialDeps?: TDeps
skipInitialOnChange?: boolean
},
) {
let deps = opts.initialDeps ?? []
let result: TResult | undefined
let isInitial = true
function memoizedFunction(): TResult {
let depTime: number
if (opts.key && opts.debug?.()) depTime = Date.now()
const newDeps = getDeps()
const depsChanged =
newDeps.length !== deps.length ||
newDeps.some((dep: any, index: number) => deps[index] !== dep)
if (!depsChanged) {
return result!
}
deps = newDeps
let resultTime: number
if (opts.key && opts.debug?.()) resultTime = Date.now()
result = fn(...newDeps)
if (opts.key && opts.debug?.()) {
const depEndTime = Math.round((Date.now() - depTime!) * 100) / 100
const resultEndTime = Math.round((Date.now() - resultTime!) * 100) / 100
const resultFpsPercentage = resultEndTime / 16
const pad = (str: number | string, num: number) => {
str = String(str)
while (str.length < num) {
str = ' ' + str
}
return str
}
console.info(
`%c⏱ ${pad(resultEndTime, 5)} /${pad(depEndTime, 5)} ms`,
`
font-size: .6rem;
font-weight: bold;
color: hsl(${Math.max(
0,
Math.min(120 - 120 * resultFpsPercentage, 120),
)}deg 100% 31%);`,
opts?.key,
)
}
if (opts?.onChange && !(isInitial && opts.skipInitialOnChange)) {
opts.onChange(result)
}
isInitial = false
return result
}
// Attach updateDeps to the function itself
memoizedFunction.updateDeps = (newDeps: [...TDeps]) => {
deps = newDeps
}
return memoizedFunction
}
export function notUndefined<T>(value: T | undefined, msg?: string): T {
if (value === undefined) {
throw new Error(`Unexpected undefined${msg ? `: ${msg}` : ''}`)
} else {
return value
}
}
export const approxEqual = (a: number, b: number) => Math.abs(a - b) < 1.01
export const debounce = (
targetWindow: Window & typeof globalThis,
fn: Function,
ms: number,
) => {
let timeoutId: number
return function (this: any, ...args: Array<any>) {
targetWindow.clearTimeout(timeoutId)
timeoutId = targetWindow.setTimeout(() => fn.apply(this, args), ms)
}
}