@vielzeug/virtualit 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dom/dom.cjs +1 -1
- package/dist/dom/dom.cjs.map +1 -1
- package/dist/dom/dom.d.ts +6 -2
- package/dist/dom/dom.d.ts.map +1 -1
- package/dist/dom/dom.js +24 -20
- package/dist/dom/dom.js.map +1 -1
- package/dist/virtualit.cjs +1 -1
- package/dist/virtualit.cjs.map +1 -1
- package/dist/virtualit.d.ts +1 -0
- package/dist/virtualit.d.ts.map +1 -1
- package/dist/virtualit.js +34 -16
- package/dist/virtualit.js.map +1 -1
- package/package.json +3 -3
package/dist/dom/dom.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require(`../virtualit.cjs`);function t(t){let n=[],r
|
|
1
|
+
const e=require(`../virtualit.cjs`);function t(t){let n=[],r=!0,i=null,a=null,o=null,s=e=>{if(typeof t.estimateSize==`number`)return t.estimateSize;let r=n[e];return r?t.estimateSize(e,r):36},c=()=>{!i||!o||(i.style.height=`${o.getTotalSize()}px`,i.style.position=`relative`,i.style.contain=`layout`)},l=()=>{i&&(t.clear?t.clear(i):i.textContent=``,i.style.height=``,i.style.position=``,i.style.contain=``)},u=(u,d)=>{let f=t.getScrollElement(),p=t.getListElement();if(!r||!f||!p||n.length===0){o?.destroy(),o=null,i=p,a=f,l();return}let m=a!==f||i!==p;i=p,a=f,!o||m?(o?.destroy(),o=e.createVirtualizer(a,{count:n.length,estimateSize:s,onChange:e=>{i&&t.render({items:n,listEl:i,virtualItems:e})},overscan:t.overscan??3})):(d&&(o.count=n.length),u&&o.invalidate()),c()};return{destroy(){o?.destroy(),o=null,l()},scrollToIndex(e,t){o?.scrollToIndex(e,t)},setActive(e){r=e,u(!1,!1)},setItems(e,t={}){let r=n.length!==e.length;n=e,u(!!t.remeasure,r)}}}exports.createDomVirtualList=t;
|
|
2
2
|
//# sourceMappingURL=dom.cjs.map
|
package/dist/dom/dom.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.cjs","names":[],"sources":["../../src/dom/dom.ts"],"sourcesContent":["import { type ScrollToIndexOptions, type VirtualItem, type Virtualizer, createVirtualizer } from '../virtualit';\n\nexport * from '../virtualit';\n\nexport type DomVirtualListRenderArgs<T> = {\n items: T[];\n listEl: HTMLElement;\n virtualItems: VirtualItem[];\n};\n\nexport type DomVirtualListOptions<T> = {\n clear
|
|
1
|
+
{"version":3,"file":"dom.cjs","names":[],"sources":["../../src/dom/dom.ts"],"sourcesContent":["import { type ScrollToIndexOptions, type VirtualItem, type Virtualizer, createVirtualizer } from '../virtualit';\n\nexport * from '../virtualit';\n\nexport type DomVirtualListRenderArgs<T> = {\n items: T[];\n listEl: HTMLElement;\n virtualItems: VirtualItem[];\n};\n\nexport type DomVirtualListOptions<T> = {\n clear?: (listEl: HTMLElement) => void;\n estimateSize: number | ((index: number, item: T) => number);\n getListElement: () => HTMLElement | null;\n getScrollElement: () => HTMLElement | null;\n overscan?: number;\n render: (args: DomVirtualListRenderArgs<T>) => void;\n};\n\nexport type DomVirtualListSetItemsOptions = {\n remeasure?: boolean;\n};\n\nexport type DomVirtualListController<T> = {\n destroy: () => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n setActive: (active: boolean) => void;\n setItems: (items: T[], options?: DomVirtualListSetItemsOptions) => void;\n};\n\nexport function createDomVirtualList<T>(options: DomVirtualListOptions<T>): DomVirtualListController<T> {\n let currentItems: T[] = [];\n let isActive = true;\n let listElRef: HTMLElement | null = null;\n let scrollElRef: HTMLElement | null = null;\n let virtualizer: Virtualizer | null = null;\n\n const resolveEstimate = (index: number): number => {\n if (typeof options.estimateSize === 'number') return options.estimateSize;\n\n const item = currentItems[index];\n\n if (!item) return 36;\n\n return options.estimateSize(index, item);\n };\n\n const applyListStyles = () => {\n if (!listElRef || !virtualizer) return;\n\n listElRef.style.height = `${virtualizer.getTotalSize()}px`;\n listElRef.style.position = 'relative';\n listElRef.style.contain = 'layout';\n };\n\n const clearAndReset = () => {\n if (!listElRef) return;\n\n if (options.clear) options.clear(listElRef);\n else listElRef.textContent = '';\n\n listElRef.style.height = '';\n listElRef.style.position = '';\n listElRef.style.contain = '';\n };\n\n const ensureVirtualizer = (remeasure: boolean, lengthChanged: boolean) => {\n const nextScroll = options.getScrollElement();\n const nextList = options.getListElement();\n\n if (!isActive || !nextScroll || !nextList || currentItems.length === 0) {\n virtualizer?.destroy();\n virtualizer = null;\n listElRef = nextList;\n scrollElRef = nextScroll;\n clearAndReset();\n\n return;\n }\n\n const targetChanged = scrollElRef !== nextScroll || listElRef !== nextList;\n\n listElRef = nextList;\n scrollElRef = nextScroll;\n\n if (!virtualizer || targetChanged) {\n virtualizer?.destroy();\n virtualizer = createVirtualizer(scrollElRef, {\n count: currentItems.length,\n estimateSize: resolveEstimate,\n onChange: (virtualItems) => {\n if (!listElRef) return;\n\n options.render({ items: currentItems, listEl: listElRef, virtualItems });\n },\n overscan: options.overscan ?? 3,\n });\n } else {\n if (lengthChanged) virtualizer.count = currentItems.length;\n\n if (remeasure) virtualizer.invalidate();\n }\n\n applyListStyles();\n };\n\n return {\n destroy() {\n virtualizer?.destroy();\n virtualizer = null;\n clearAndReset();\n },\n scrollToIndex(index, scrollOptions) {\n virtualizer?.scrollToIndex(index, scrollOptions);\n },\n setActive(active) {\n isActive = active;\n\n ensureVirtualizer(false, false);\n },\n setItems(items, setItemsOptions = {}) {\n const lengthChanged = currentItems.length !== items.length;\n\n currentItems = items;\n\n ensureVirtualizer(!!setItemsOptions.remeasure, lengthChanged);\n },\n };\n}\n"],"mappings":"oCA8BA,SAAgB,EAAwB,EAAgE,CACtG,IAAI,EAAoB,EAAE,CACtB,EAAW,GACX,EAAgC,KAChC,EAAkC,KAClC,EAAkC,KAEhC,EAAmB,GAA0B,CACjD,GAAI,OAAO,EAAQ,cAAiB,SAAU,OAAO,EAAQ,aAE7D,IAAM,EAAO,EAAa,GAI1B,OAFK,EAEE,EAAQ,aAAa,EAAO,EAAK,CAFtB,IAKd,MAAwB,CACxB,CAAC,GAAa,CAAC,IAEnB,EAAU,MAAM,OAAS,GAAG,EAAY,cAAc,CAAC,IACvD,EAAU,MAAM,SAAW,WAC3B,EAAU,MAAM,QAAU,WAGtB,MAAsB,CACrB,IAED,EAAQ,MAAO,EAAQ,MAAM,EAAU,CACtC,EAAU,YAAc,GAE7B,EAAU,MAAM,OAAS,GACzB,EAAU,MAAM,SAAW,GAC3B,EAAU,MAAM,QAAU,KAGtB,GAAqB,EAAoB,IAA2B,CACxE,IAAM,EAAa,EAAQ,kBAAkB,CACvC,EAAW,EAAQ,gBAAgB,CAEzC,GAAI,CAAC,GAAY,CAAC,GAAc,CAAC,GAAY,EAAa,SAAW,EAAG,CACtE,GAAa,SAAS,CACtB,EAAc,KACd,EAAY,EACZ,EAAc,EACd,GAAe,CAEf,OAGF,IAAM,EAAgB,IAAgB,GAAc,IAAc,EAElE,EAAY,EACZ,EAAc,EAEV,CAAC,GAAe,GAClB,GAAa,SAAS,CACtB,EAAc,EAAA,kBAAkB,EAAa,CAC3C,MAAO,EAAa,OACpB,aAAc,EACd,SAAW,GAAiB,CACrB,GAEL,EAAQ,OAAO,CAAE,MAAO,EAAc,OAAQ,EAAW,eAAc,CAAC,EAE1E,SAAU,EAAQ,UAAY,EAC/B,CAAC,GAEE,IAAe,EAAY,MAAQ,EAAa,QAEhD,GAAW,EAAY,YAAY,EAGzC,GAAiB,EAGnB,MAAO,CACL,SAAU,CACR,GAAa,SAAS,CACtB,EAAc,KACd,GAAe,EAEjB,cAAc,EAAO,EAAe,CAClC,GAAa,cAAc,EAAO,EAAc,EAElD,UAAU,EAAQ,CAChB,EAAW,EAEX,EAAkB,GAAO,GAAM,EAEjC,SAAS,EAAO,EAAkB,EAAE,CAAE,CACpC,IAAM,EAAgB,EAAa,SAAW,EAAM,OAEpD,EAAe,EAEf,EAAkB,CAAC,CAAC,EAAgB,UAAW,EAAc,EAEhE"}
|
package/dist/dom/dom.d.ts
CHANGED
|
@@ -6,17 +6,21 @@ export type DomVirtualListRenderArgs<T> = {
|
|
|
6
6
|
virtualItems: VirtualItem[];
|
|
7
7
|
};
|
|
8
8
|
export type DomVirtualListOptions<T> = {
|
|
9
|
-
clear
|
|
9
|
+
clear?: (listEl: HTMLElement) => void;
|
|
10
10
|
estimateSize: number | ((index: number, item: T) => number);
|
|
11
11
|
getListElement: () => HTMLElement | null;
|
|
12
12
|
getScrollElement: () => HTMLElement | null;
|
|
13
13
|
overscan?: number;
|
|
14
14
|
render: (args: DomVirtualListRenderArgs<T>) => void;
|
|
15
15
|
};
|
|
16
|
+
export type DomVirtualListSetItemsOptions = {
|
|
17
|
+
remeasure?: boolean;
|
|
18
|
+
};
|
|
16
19
|
export type DomVirtualListController<T> = {
|
|
17
20
|
destroy: () => void;
|
|
18
21
|
scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;
|
|
19
|
-
|
|
22
|
+
setActive: (active: boolean) => void;
|
|
23
|
+
setItems: (items: T[], options?: DomVirtualListSetItemsOptions) => void;
|
|
20
24
|
};
|
|
21
25
|
export declare function createDomVirtualList<T>(options: DomVirtualListOptions<T>): DomVirtualListController<T>;
|
|
22
26
|
//# sourceMappingURL=dom.d.ts.map
|
package/dist/dom/dom.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/dom/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,WAAW,EAAuC,MAAM,cAAc,CAAC;AAEhH,cAAc,cAAc,CAAC;AAE7B,MAAM,MAAM,wBAAwB,CAAC,CAAC,IAAI;IACxC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,WAAW,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI;IACrC,KAAK,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/dom/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,WAAW,EAAuC,MAAM,cAAc,CAAC;AAEhH,cAAc,cAAc,CAAC;AAE7B,MAAM,MAAM,wBAAwB,CAAC,CAAC,IAAI;IACxC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,WAAW,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI;IACrC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC;IAC5D,cAAc,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACzC,gBAAgB,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,CAAC,IAAI,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,wBAAwB,CAAC,CAAC,IAAI;IACxC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACvE,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,6BAA6B,KAAK,IAAI,CAAC;CACzE,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,wBAAwB,CAAC,CAAC,CAAC,CAkGtG"}
|
package/dist/dom/dom.js
CHANGED
|
@@ -1,43 +1,47 @@
|
|
|
1
1
|
import { createVirtualizer as e } from "../virtualit.js";
|
|
2
2
|
//#region src/dom/dom.ts
|
|
3
3
|
function t(t) {
|
|
4
|
-
let n = [], r =
|
|
5
|
-
|
|
4
|
+
let n = [], r = !0, i = null, a = null, o = null, s = (e) => {
|
|
5
|
+
if (typeof t.estimateSize == "number") return t.estimateSize;
|
|
6
|
+
let r = n[e];
|
|
7
|
+
return r ? t.estimateSize(e, r) : 36;
|
|
6
8
|
}, c = () => {
|
|
7
|
-
|
|
9
|
+
!i || !o || (i.style.height = `${o.getTotalSize()}px`, i.style.position = "relative", i.style.contain = "layout");
|
|
8
10
|
}, l = () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
i && (t.clear ? t.clear(i) : i.textContent = "", i.style.height = "", i.style.position = "", i.style.contain = "");
|
|
12
|
+
}, u = (u, d) => {
|
|
13
|
+
let f = t.getScrollElement(), p = t.getListElement();
|
|
14
|
+
if (!r || !f || !p || n.length === 0) {
|
|
15
|
+
o?.destroy(), o = null, i = p, a = f, l();
|
|
12
16
|
return;
|
|
13
17
|
}
|
|
14
|
-
let
|
|
15
|
-
|
|
18
|
+
let m = a !== f || i !== p;
|
|
19
|
+
i = p, a = f, !o || m ? (o?.destroy(), o = e(a, {
|
|
16
20
|
count: n.length,
|
|
17
|
-
estimateSize:
|
|
21
|
+
estimateSize: s,
|
|
18
22
|
onChange: (e) => {
|
|
19
|
-
|
|
23
|
+
i && t.render({
|
|
20
24
|
items: n,
|
|
21
|
-
listEl:
|
|
25
|
+
listEl: i,
|
|
22
26
|
virtualItems: e
|
|
23
27
|
});
|
|
24
28
|
},
|
|
25
29
|
overscan: t.overscan ?? 3
|
|
26
|
-
})) : (
|
|
30
|
+
})) : (d && (o.count = n.length), u && o.invalidate()), c();
|
|
27
31
|
};
|
|
28
32
|
return {
|
|
29
33
|
destroy() {
|
|
30
|
-
|
|
34
|
+
o?.destroy(), o = null, l();
|
|
31
35
|
},
|
|
32
36
|
scrollToIndex(e, t) {
|
|
33
|
-
|
|
37
|
+
o?.scrollToIndex(e, t);
|
|
34
38
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
setActive(e) {
|
|
40
|
+
r = e, u(!1, !1);
|
|
41
|
+
},
|
|
42
|
+
setItems(e, t = {}) {
|
|
43
|
+
let r = n.length !== e.length;
|
|
44
|
+
n = e, u(!!t.remeasure, r);
|
|
41
45
|
}
|
|
42
46
|
};
|
|
43
47
|
}
|
package/dist/dom/dom.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.js","names":[],"sources":["../../src/dom/dom.ts"],"sourcesContent":["import { type ScrollToIndexOptions, type VirtualItem, type Virtualizer, createVirtualizer } from '../virtualit';\n\nexport * from '../virtualit';\n\nexport type DomVirtualListRenderArgs<T> = {\n items: T[];\n listEl: HTMLElement;\n virtualItems: VirtualItem[];\n};\n\nexport type DomVirtualListOptions<T> = {\n clear
|
|
1
|
+
{"version":3,"file":"dom.js","names":[],"sources":["../../src/dom/dom.ts"],"sourcesContent":["import { type ScrollToIndexOptions, type VirtualItem, type Virtualizer, createVirtualizer } from '../virtualit';\n\nexport * from '../virtualit';\n\nexport type DomVirtualListRenderArgs<T> = {\n items: T[];\n listEl: HTMLElement;\n virtualItems: VirtualItem[];\n};\n\nexport type DomVirtualListOptions<T> = {\n clear?: (listEl: HTMLElement) => void;\n estimateSize: number | ((index: number, item: T) => number);\n getListElement: () => HTMLElement | null;\n getScrollElement: () => HTMLElement | null;\n overscan?: number;\n render: (args: DomVirtualListRenderArgs<T>) => void;\n};\n\nexport type DomVirtualListSetItemsOptions = {\n remeasure?: boolean;\n};\n\nexport type DomVirtualListController<T> = {\n destroy: () => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n setActive: (active: boolean) => void;\n setItems: (items: T[], options?: DomVirtualListSetItemsOptions) => void;\n};\n\nexport function createDomVirtualList<T>(options: DomVirtualListOptions<T>): DomVirtualListController<T> {\n let currentItems: T[] = [];\n let isActive = true;\n let listElRef: HTMLElement | null = null;\n let scrollElRef: HTMLElement | null = null;\n let virtualizer: Virtualizer | null = null;\n\n const resolveEstimate = (index: number): number => {\n if (typeof options.estimateSize === 'number') return options.estimateSize;\n\n const item = currentItems[index];\n\n if (!item) return 36;\n\n return options.estimateSize(index, item);\n };\n\n const applyListStyles = () => {\n if (!listElRef || !virtualizer) return;\n\n listElRef.style.height = `${virtualizer.getTotalSize()}px`;\n listElRef.style.position = 'relative';\n listElRef.style.contain = 'layout';\n };\n\n const clearAndReset = () => {\n if (!listElRef) return;\n\n if (options.clear) options.clear(listElRef);\n else listElRef.textContent = '';\n\n listElRef.style.height = '';\n listElRef.style.position = '';\n listElRef.style.contain = '';\n };\n\n const ensureVirtualizer = (remeasure: boolean, lengthChanged: boolean) => {\n const nextScroll = options.getScrollElement();\n const nextList = options.getListElement();\n\n if (!isActive || !nextScroll || !nextList || currentItems.length === 0) {\n virtualizer?.destroy();\n virtualizer = null;\n listElRef = nextList;\n scrollElRef = nextScroll;\n clearAndReset();\n\n return;\n }\n\n const targetChanged = scrollElRef !== nextScroll || listElRef !== nextList;\n\n listElRef = nextList;\n scrollElRef = nextScroll;\n\n if (!virtualizer || targetChanged) {\n virtualizer?.destroy();\n virtualizer = createVirtualizer(scrollElRef, {\n count: currentItems.length,\n estimateSize: resolveEstimate,\n onChange: (virtualItems) => {\n if (!listElRef) return;\n\n options.render({ items: currentItems, listEl: listElRef, virtualItems });\n },\n overscan: options.overscan ?? 3,\n });\n } else {\n if (lengthChanged) virtualizer.count = currentItems.length;\n\n if (remeasure) virtualizer.invalidate();\n }\n\n applyListStyles();\n };\n\n return {\n destroy() {\n virtualizer?.destroy();\n virtualizer = null;\n clearAndReset();\n },\n scrollToIndex(index, scrollOptions) {\n virtualizer?.scrollToIndex(index, scrollOptions);\n },\n setActive(active) {\n isActive = active;\n\n ensureVirtualizer(false, false);\n },\n setItems(items, setItemsOptions = {}) {\n const lengthChanged = currentItems.length !== items.length;\n\n currentItems = items;\n\n ensureVirtualizer(!!setItemsOptions.remeasure, lengthChanged);\n },\n };\n}\n"],"mappings":";;AA8BA,SAAgB,EAAwB,GAAgE;CACtG,IAAI,IAAoB,EAAE,EACtB,IAAW,IACX,IAAgC,MAChC,IAAkC,MAClC,IAAkC,MAEhC,KAAmB,MAA0B;AACjD,MAAI,OAAO,EAAQ,gBAAiB,SAAU,QAAO,EAAQ;EAE7D,IAAM,IAAO,EAAa;AAI1B,SAFK,IAEE,EAAQ,aAAa,GAAO,EAAK,GAFtB;IAKd,UAAwB;AACxB,GAAC,KAAa,CAAC,MAEnB,EAAU,MAAM,SAAS,GAAG,EAAY,cAAc,CAAC,KACvD,EAAU,MAAM,WAAW,YAC3B,EAAU,MAAM,UAAU;IAGtB,UAAsB;AACrB,QAED,EAAQ,QAAO,EAAQ,MAAM,EAAU,GACtC,EAAU,cAAc,IAE7B,EAAU,MAAM,SAAS,IACzB,EAAU,MAAM,WAAW,IAC3B,EAAU,MAAM,UAAU;IAGtB,KAAqB,GAAoB,MAA2B;EACxE,IAAM,IAAa,EAAQ,kBAAkB,EACvC,IAAW,EAAQ,gBAAgB;AAEzC,MAAI,CAAC,KAAY,CAAC,KAAc,CAAC,KAAY,EAAa,WAAW,GAAG;AAKtE,GAJA,GAAa,SAAS,EACtB,IAAc,MACd,IAAY,GACZ,IAAc,GACd,GAAe;AAEf;;EAGF,IAAM,IAAgB,MAAgB,KAAc,MAAc;AAuBlE,EArBA,IAAY,GACZ,IAAc,GAEV,CAAC,KAAe,KAClB,GAAa,SAAS,EACtB,IAAc,EAAkB,GAAa;GAC3C,OAAO,EAAa;GACpB,cAAc;GACd,WAAW,MAAiB;AACrB,SAEL,EAAQ,OAAO;KAAE,OAAO;KAAc,QAAQ;KAAW;KAAc,CAAC;;GAE1E,UAAU,EAAQ,YAAY;GAC/B,CAAC,KAEE,MAAe,EAAY,QAAQ,EAAa,SAEhD,KAAW,EAAY,YAAY,GAGzC,GAAiB;;AAGnB,QAAO;EACL,UAAU;AAGR,GAFA,GAAa,SAAS,EACtB,IAAc,MACd,GAAe;;EAEjB,cAAc,GAAO,GAAe;AAClC,MAAa,cAAc,GAAO,EAAc;;EAElD,UAAU,GAAQ;AAGhB,GAFA,IAAW,GAEX,EAAkB,IAAO,GAAM;;EAEjC,SAAS,GAAO,IAAkB,EAAE,EAAE;GACpC,IAAM,IAAgB,EAAa,WAAW,EAAM;AAIpD,GAFA,IAAe,GAEf,EAAkB,CAAC,CAAC,EAAgB,WAAW,EAAc;;EAEhE"}
|
package/dist/virtualit.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=class{_count;_estimateSizeFn;overscan;onChange;measuredHeights=new Map;virtualItems=[];totalSize=0;scrollOffsets=new Float64Array;containerHeight=0;scrollTop=0;prevRenderStart=-1;prevRenderEnd=-1;attachedEl=null;resizeObserver=null;scrollHandler=null;pendingBuild=!1;constructor(
|
|
1
|
+
var e=36,t=3;function n(e,t=0){return Number.isFinite(e)?Math.max(0,Math.floor(e)):t}function r(e,t){return!Number.isFinite(e)||e<=0?t:e}function i(t,n=e){if(typeof t==`number`){let e=r(t,n);return()=>e}return e=>r(t(e),n)}var a=class{_count;_estimateSizeFn;overscan;onChange;measuredHeights=new Map;virtualItems=[];totalSize=0;scrollOffsets=new Float64Array;containerHeight=0;scrollTop=0;prevRenderStart=-1;prevRenderEnd=-1;attachedEl=null;resizeObserver=null;scrollHandler=null;pendingBuild=!1;constructor(r){this._count=n(r.count),this._estimateSizeFn=i(r.estimateSize??e),this.overscan=n(r.overscan??t,t),this.onChange=r.onChange,this.buildOffsets()}get count(){return this._count}set count(e){let t=n(e);t!==this._count&&(this._count=t,this.buildOffsets(),this.attachedEl&&this.computeVisible())}set estimateSize(e){this._estimateSizeFn=i(e),this.measuredHeights.clear(),this.buildOffsets(),this.attachedEl&&this.computeVisible()}attach(e){this.teardown(),this.attachedEl=e,this.containerHeight=e.clientHeight,this.scrollTop=e.scrollTop,this.scrollHandler=()=>{this.scrollTop=e.scrollTop,this.computeVisible()},e.addEventListener(`scroll`,this.scrollHandler,{passive:!0}),this.resizeObserver=new ResizeObserver(()=>{this.containerHeight=e.clientHeight,this.computeVisible()}),this.resizeObserver.observe(e),this.computeVisible()}destroy(){this.teardown()}[Symbol.dispose](){this.destroy()}getVirtualItems(){return this.virtualItems}getTotalSize(){return this.totalSize}measureElement(e,t){let i=n(e,-1),a=r(t,-1);i<0||i>=this._count||a<=0||this.heightAt(i)!==a&&(this.measuredHeights.set(i,a),this.pendingBuild||(this.pendingBuild=!0,queueMicrotask(()=>{this.pendingBuild=!1,this.buildOffsets(),this.attachedEl&&this.computeVisible()})))}scrollToIndex(e,t={}){let n=this.attachedEl;if(!n||this._count<=0)return;let r=Math.max(0,Math.min(Number.isFinite(e)?Math.floor(e):0,this._count-1)),i=t.align??`auto`,a=t.behavior??`auto`,o=this.offsetAt(r),s=this.heightAt(r),c;if(i===`start`)c=o;else if(i===`end`)c=o+s-this.containerHeight;else if(i===`center`)c=o-(this.containerHeight-s)/2;else{let e=n.scrollTop,t=e+this.containerHeight;if(o>=e&&o+s<=t)return;c=o<e?o:o+s-this.containerHeight}n.scrollTo({behavior:a,top:this.clampScrollTop(c)})}scrollToOffset(e,t={}){this.attachedEl?.scrollTo({behavior:t.behavior??`auto`,top:this.clampScrollTop(e)})}invalidate(){this.measuredHeights.clear(),this.buildOffsets(),this.attachedEl&&this.computeVisible()}teardown(){this.scrollHandler&&this.attachedEl&&(this.attachedEl.removeEventListener(`scroll`,this.scrollHandler),this.scrollHandler=null),this.resizeObserver?.disconnect(),this.resizeObserver=null,this.attachedEl=null}heightAt(e){return this.measuredHeights.get(e)??this._estimateSizeFn(e)}offsetAt(e){return this.scrollOffsets[e]??0}clampScrollTop(e){let t=Number.isFinite(e)?e:0,n=Math.max(0,this.totalSize-this.containerHeight);return Math.min(n,Math.max(0,t))}buildOffsets(){this.prevRenderStart=-1,this.prevRenderEnd=-1;let e=new Float64Array(this._count+1);e[0]=0;for(let t=0;t<this._count;t++)e[t+1]=e[t]+this.heightAt(t);this.scrollOffsets=e,this.totalSize=e[this._count]??0}computeVisible(){let e=this.scrollTop,t=e+this.containerHeight,n=0,r=this._count-1;for(;n<r;){let t=n+r>>1;this.scrollOffsets[t+1]<=e?n=t+1:r=t}let i=n,a=i,o=this._count-1;for(;a<o;){let e=a+o+1>>1;this.scrollOffsets[e]<t?a=e:o=e-1}let s=a,c=Math.max(0,i-this.overscan),l=Math.min(this._count-1,s+this.overscan);if(c===this.prevRenderStart&&l===this.prevRenderEnd)return;this.prevRenderStart=c,this.prevRenderEnd=l;let u=[];for(let e=c;e<=l;e++)u.push({height:this.heightAt(e),index:e,top:this.scrollOffsets[e]});this.virtualItems=u,this.onChange?.(u,this.totalSize)}};function o(e,t){let n=new a(t);return n.attach(e),n}exports.Virtualizer=a,exports.createVirtualizer=o;
|
|
2
2
|
//# sourceMappingURL=virtualit.cjs.map
|
package/dist/virtualit.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualit.cjs","names":[],"sources":["../src/virtualit.ts"],"sourcesContent":["/**\n * @vielzeug/virtualit — Lightweight virtual list / infinite-scroll engine.\n *\n * Framework-agnostic: works with any DOM rendering layer.\n * Uses a `ResizeObserver` to re-measure the scroll container and a\n * `scroll` listener to update the visible window.\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualItem {\n /** Original item index in the full list */\n index: number;\n /** Pixel offset from the top of the virtual scroll area */\n top: number;\n /** Measured (or estimated) pixel height for this item */\n height: number;\n}\n\nexport interface VirtualizerOptions {\n /** Total number of items. */\n count: number;\n /**\n * Either a fixed row height or a per-index estimator function.\n * Defaults to 36px.\n */\n estimateSize?: number | ((index: number) => number);\n /**\n * Number of items to render outside the visible viewport on each side.\n * Higher values reduce blank-flash during fast scroll at the cost of more DOM nodes.\n * Defaults to 3.\n */\n overscan?: number;\n /**\n * Called whenever the visible range changes. Trigger your re-render here.\n */\n onChange?: (items: VirtualItem[], totalSize: number) => void;\n}\n\nexport interface ScrollToIndexOptions {\n /** 'start' | 'end' | 'center' | 'auto'. Defaults to 'auto'. */\n align?: 'start' | 'end' | 'center' | 'auto';\n /** Scroll behaviour. Defaults to 'auto'. */\n behavior?: ScrollBehavior;\n}\n\n// ─── Virtualizer ──────────────────────────────────────────────────────────────\n\nexport class Virtualizer {\n // mutable options\n private _count: number;\n private _estimateSizeFn: (index: number) => number;\n private overscan: number;\n private onChange: ((items: VirtualItem[], totalSize: number) => void) | undefined;\n\n // internal state\n private measuredHeights: Map<number, number> = new Map();\n private virtualItems: VirtualItem[] = [];\n private totalSize = 0;\n private scrollOffsets: Float64Array = new Float64Array(0); // prefix-sum cache\n private containerHeight = 0;\n private scrollTop = 0;\n\n // render range cache — reset in buildOffsets() so layout changes always re-render\n private prevRenderStart = -1;\n private prevRenderEnd = -1;\n\n // cleanup handles\n private attachedEl: HTMLElement | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private scrollHandler: (() => void) | null = null;\n\n // batching flag for measureElement\n private pendingBuild = false;\n\n constructor(options: VirtualizerOptions) {\n this._count = options.count;\n\n const est = options.estimateSize ?? 36;\n\n this._estimateSizeFn = typeof est === 'number' ? () => est : est;\n this.overscan = options.overscan ?? 3;\n this.onChange = options.onChange;\n // Build the offset table eagerly; computeVisible is deferred to attach()\n // so the first onChange call always has a real containerHeight.\n this.buildOffsets();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────\n\n get count(): number {\n return this._count;\n }\n\n /** Setting count automatically rebuilds offsets and triggers a re-render. */\n set count(value: number) {\n this._count = value;\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n /**\n * Update the size estimator. Clears all measured heights and re-renders.\n * Useful when switching between row density modes (e.g. compact ↔ comfortable).\n */\n set estimateSize(fn: number | ((index: number) => number)) {\n this._estimateSizeFn = typeof fn === 'number' ? () => fn : fn;\n this.measuredHeights.clear();\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n /** Start observing the scroll container. */\n attach(el: HTMLElement): void {\n this.teardown();\n\n this.attachedEl = el;\n this.containerHeight = el.clientHeight;\n this.scrollTop = el.scrollTop;\n\n this.scrollHandler = () => {\n this.scrollTop = el.scrollTop;\n this.computeVisible();\n };\n el.addEventListener('scroll', this.scrollHandler, { passive: true });\n\n this.resizeObserver = new ResizeObserver(() => {\n this.containerHeight = el.clientHeight;\n // The offset table depends only on item heights, not container height —\n // no need to rebuild it here, only recompute the visible window.\n this.computeVisible();\n });\n this.resizeObserver.observe(el);\n\n this.computeVisible();\n }\n\n /** Stop observing and remove all listeners. */\n destroy(): void {\n this.teardown();\n }\n\n /** Supports the Explicit Resource Management `using` keyword. */\n [Symbol.dispose](): void {\n this.destroy();\n }\n\n /** Returns the currently visible virtual items. */\n getVirtualItems(): VirtualItem[] {\n return this.virtualItems;\n }\n\n /** Total pixel height of the entire list (set as the spacer height). */\n getTotalSize(): number {\n return this.totalSize;\n }\n\n /**\n * Record a measured height for a rendered item (for variable-height lists).\n *\n * Measurements are batched via microtask — safe to call for every item in a\n * render loop without incurring O(n²) rebuilds.\n */\n measureElement(index: number, height: number): void {\n if (this.heightAt(index) === height) return;\n\n this.measuredHeights.set(index, height);\n\n if (!this.pendingBuild) {\n this.pendingBuild = true;\n queueMicrotask(() => {\n this.pendingBuild = false;\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n });\n }\n }\n\n /** Programmatically scroll to a specific index. */\n scrollToIndex(index: number, options: ScrollToIndexOptions = {}): void {\n const el = this.attachedEl;\n\n if (!el) return;\n\n const clampedIndex = Math.max(0, Math.min(index, this._count - 1));\n const align = options.align ?? 'auto';\n const behavior = options.behavior ?? 'auto';\n const itemTop = this.offsetAt(clampedIndex);\n const itemHeight = this.heightAt(clampedIndex);\n\n let targetScrollTop: number;\n\n if (align === 'start') {\n targetScrollTop = itemTop;\n } else if (align === 'end') {\n targetScrollTop = itemTop + itemHeight - this.containerHeight;\n } else if (align === 'center') {\n targetScrollTop = itemTop - (this.containerHeight - itemHeight) / 2;\n } else {\n // auto: scroll only if not already visible\n const visibleStart = el.scrollTop;\n const visibleEnd = visibleStart + this.containerHeight;\n\n if (itemTop >= visibleStart && itemTop + itemHeight <= visibleEnd) return;\n\n targetScrollTop = itemTop < visibleStart ? itemTop : itemTop + itemHeight - this.containerHeight;\n }\n\n el.scrollTo({ behavior, top: Math.max(0, targetScrollTop) });\n }\n\n /** Programmatically scroll to a specific pixel offset. */\n scrollToOffset(offset: number, options: { behavior?: ScrollBehavior } = {}): void {\n this.attachedEl?.scrollTo({ behavior: options.behavior ?? 'auto', top: Math.max(0, offset) });\n }\n\n /**\n * Invalidate all item measurements. Call after a font load or layout shift\n * that changes item heights.\n */\n invalidate(): void {\n this.measuredHeights.clear();\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n // ─── Private Helpers ──────────────────────────────────────────────────────\n\n private teardown(): void {\n if (this.scrollHandler && this.attachedEl) {\n this.attachedEl.removeEventListener('scroll', this.scrollHandler);\n this.scrollHandler = null;\n }\n\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.attachedEl = null;\n }\n\n private heightAt(index: number): number {\n return this.measuredHeights.get(index) ?? this._estimateSizeFn(index);\n }\n\n private offsetAt(index: number): number {\n return this.scrollOffsets[index] ?? 0;\n }\n\n private buildOffsets(): void {\n // Invalidate the render range cache: item positions may shift even when the\n // visible index range stays the same (e.g. an item above grew taller).\n this.prevRenderStart = -1;\n this.prevRenderEnd = -1;\n\n const offsets = new Float64Array(this._count + 1);\n\n offsets[0] = 0;\n for (let i = 0; i < this._count; i++) {\n offsets[i + 1] = offsets[i] + this.heightAt(i);\n }\n this.scrollOffsets = offsets;\n this.totalSize = offsets[this._count] ?? 0;\n }\n\n private computeVisible(): void {\n const start = this.scrollTop;\n const end = start + this.containerHeight;\n\n // Binary search for the first visible index\n let lo = 0;\n let hi = this._count - 1;\n\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n\n if (this.scrollOffsets[mid + 1] <= start) lo = mid + 1;\n else hi = mid;\n }\n\n const firstVisible = lo;\n\n // Binary search for the last visible index\n let lo2 = firstVisible;\n let hi2 = this._count - 1;\n\n while (lo2 < hi2) {\n const mid = (lo2 + hi2 + 1) >> 1;\n\n if (this.scrollOffsets[mid] < end) lo2 = mid;\n else hi2 = mid - 1;\n }\n\n const lastVisible = lo2;\n const renderStart = Math.max(0, firstVisible - this.overscan);\n const renderEnd = Math.min(this._count - 1, lastVisible + this.overscan);\n\n // Skip re-render when the range is unchanged (e.g. a sub-pixel scroll that\n // doesn't cross an item boundary). The cache is reset in buildOffsets() so\n // any layout change always produces at least one render.\n if (renderStart === this.prevRenderStart && renderEnd === this.prevRenderEnd) return;\n\n this.prevRenderStart = renderStart;\n this.prevRenderEnd = renderEnd;\n\n const items: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n items.push({ height: this.heightAt(i), index: i, top: this.scrollOffsets[i] });\n }\n\n this.virtualItems = items;\n this.onChange?.(items, this.totalSize);\n }\n}\n\n// ─── Convenience factory ──────────────────────────────────────────────────────\n\n/**\n * Creates and immediately attaches a `Virtualizer` to the given scroll container.\n *\n * @example\n * ```ts\n * import { createVirtualizer } from '@vielzeug/virtualit';\n *\n * const virt = createVirtualizer(scrollContainerEl, {\n * count: items.length,\n * estimateSize: 36,\n * onChange: (virtualItems, totalSize) => {\n * // update your rendered list\n * },\n * });\n *\n * // Later:\n * virt.destroy();\n *\n * // Or, with the Explicit Resource Management proposal:\n * {\n * using virt = createVirtualizer(scrollContainerEl, { ... });\n * } // virt.destroy() called automatically\n * ```\n */\nexport function createVirtualizer(el: HTMLElement, options: VirtualizerOptions): Virtualizer {\n const v = new Virtualizer(options);\n\n v.attach(el);\n\n return v;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"virtualit.cjs","names":[],"sources":["../src/virtualit.ts"],"sourcesContent":["/**\n * @vielzeug/virtualit — Lightweight virtual list / infinite-scroll engine.\n *\n * Framework-agnostic: works with any DOM rendering layer.\n * Uses a `ResizeObserver` to re-measure the scroll container and a\n * `scroll` listener to update the visible window.\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualItem {\n /** Original item index in the full list */\n index: number;\n /** Pixel offset from the top of the virtual scroll area */\n top: number;\n /** Measured (or estimated) pixel height for this item */\n height: number;\n}\n\nexport interface VirtualizerOptions {\n /** Total number of items. */\n count: number;\n /**\n * Either a fixed row height or a per-index estimator function.\n * Defaults to 36px.\n */\n estimateSize?: number | ((index: number) => number);\n /**\n * Number of items to render outside the visible viewport on each side.\n * Higher values reduce blank-flash during fast scroll at the cost of more DOM nodes.\n * Defaults to 3.\n */\n overscan?: number;\n /**\n * Called whenever the visible range changes. Trigger your re-render here.\n */\n onChange?: (items: VirtualItem[], totalSize: number) => void;\n}\n\nexport interface ScrollToIndexOptions {\n /** 'start' | 'end' | 'center' | 'auto'. Defaults to 'auto'. */\n align?: 'start' | 'end' | 'center' | 'auto';\n /** Scroll behaviour. Defaults to 'auto'. */\n behavior?: ScrollBehavior;\n}\n\nconst DEFAULT_ESTIMATE_SIZE = 36;\nconst DEFAULT_OVERSCAN = 3;\n\nfunction toNonNegativeInt(value: number, fallback = 0): number {\n if (!Number.isFinite(value)) return fallback;\n\n return Math.max(0, Math.floor(value));\n}\n\nfunction toPositiveNumber(value: number, fallback: number): number {\n if (!Number.isFinite(value) || value <= 0) return fallback;\n\n return value;\n}\n\nfunction createEstimateSizeFn(\n estimate: number | ((index: number) => number),\n fallback = DEFAULT_ESTIMATE_SIZE,\n): (index: number) => number {\n if (typeof estimate === 'number') {\n const size = toPositiveNumber(estimate, fallback);\n\n return () => size;\n }\n\n return (index: number) => toPositiveNumber(estimate(index), fallback);\n}\n\n// ─── Virtualizer ──────────────────────────────────────────────────────────────\n\nexport class Virtualizer {\n // mutable options\n private _count: number;\n private _estimateSizeFn: (index: number) => number;\n private overscan: number;\n private onChange: ((items: VirtualItem[], totalSize: number) => void) | undefined;\n\n // internal state\n private measuredHeights: Map<number, number> = new Map();\n private virtualItems: VirtualItem[] = [];\n private totalSize = 0;\n private scrollOffsets: Float64Array = new Float64Array(0); // prefix-sum cache\n private containerHeight = 0;\n private scrollTop = 0;\n\n // render range cache — reset in buildOffsets() so layout changes always re-render\n private prevRenderStart = -1;\n private prevRenderEnd = -1;\n\n // cleanup handles\n private attachedEl: HTMLElement | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private scrollHandler: (() => void) | null = null;\n\n // batching flag for measureElement\n private pendingBuild = false;\n\n constructor(options: VirtualizerOptions) {\n this._count = toNonNegativeInt(options.count);\n\n const est = options.estimateSize ?? DEFAULT_ESTIMATE_SIZE;\n\n this._estimateSizeFn = createEstimateSizeFn(est);\n this.overscan = toNonNegativeInt(options.overscan ?? DEFAULT_OVERSCAN, DEFAULT_OVERSCAN);\n this.onChange = options.onChange;\n // Build the offset table eagerly; computeVisible is deferred to attach()\n // so the first onChange call always has a real containerHeight.\n this.buildOffsets();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────\n\n get count(): number {\n return this._count;\n }\n\n /** Setting count automatically rebuilds offsets and triggers a re-render. */\n set count(value: number) {\n const nextCount = toNonNegativeInt(value);\n\n if (nextCount === this._count) return;\n\n this._count = nextCount;\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n /**\n * Update the size estimator. Clears all measured heights and re-renders.\n * Useful when switching between row density modes (e.g. compact ↔ comfortable).\n */\n set estimateSize(fn: number | ((index: number) => number)) {\n this._estimateSizeFn = createEstimateSizeFn(fn);\n this.measuredHeights.clear();\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n /** Start observing the scroll container. */\n attach(el: HTMLElement): void {\n this.teardown();\n\n this.attachedEl = el;\n this.containerHeight = el.clientHeight;\n this.scrollTop = el.scrollTop;\n\n this.scrollHandler = () => {\n this.scrollTop = el.scrollTop;\n this.computeVisible();\n };\n el.addEventListener('scroll', this.scrollHandler, { passive: true });\n\n this.resizeObserver = new ResizeObserver(() => {\n this.containerHeight = el.clientHeight;\n // The offset table depends only on item heights, not container height —\n // no need to rebuild it here, only recompute the visible window.\n this.computeVisible();\n });\n this.resizeObserver.observe(el);\n\n this.computeVisible();\n }\n\n /** Stop observing and remove all listeners. */\n destroy(): void {\n this.teardown();\n }\n\n /** Supports the Explicit Resource Management `using` keyword. */\n [Symbol.dispose](): void {\n this.destroy();\n }\n\n /** Returns the currently visible virtual items. */\n getVirtualItems(): VirtualItem[] {\n return this.virtualItems;\n }\n\n /** Total pixel height of the entire list (set as the spacer height). */\n getTotalSize(): number {\n return this.totalSize;\n }\n\n /**\n * Record a measured height for a rendered item (for variable-height lists).\n *\n * Measurements are batched via microtask — safe to call for every item in a\n * render loop without incurring O(n²) rebuilds.\n */\n measureElement(index: number, height: number): void {\n const safeIndex = toNonNegativeInt(index, -1);\n const safeHeight = toPositiveNumber(height, -1);\n\n if (safeIndex < 0 || safeIndex >= this._count || safeHeight <= 0) return;\n\n if (this.heightAt(safeIndex) === safeHeight) return;\n\n this.measuredHeights.set(safeIndex, safeHeight);\n\n if (!this.pendingBuild) {\n this.pendingBuild = true;\n queueMicrotask(() => {\n this.pendingBuild = false;\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n });\n }\n }\n\n /** Programmatically scroll to a specific index. */\n scrollToIndex(index: number, options: ScrollToIndexOptions = {}): void {\n const el = this.attachedEl;\n\n if (!el || this._count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, this._count - 1));\n const align = options.align ?? 'auto';\n const behavior = options.behavior ?? 'auto';\n const itemTop = this.offsetAt(clampedIndex);\n const itemHeight = this.heightAt(clampedIndex);\n\n let targetScrollTop: number;\n\n if (align === 'start') {\n targetScrollTop = itemTop;\n } else if (align === 'end') {\n targetScrollTop = itemTop + itemHeight - this.containerHeight;\n } else if (align === 'center') {\n targetScrollTop = itemTop - (this.containerHeight - itemHeight) / 2;\n } else {\n // auto: scroll only if not already visible\n const visibleStart = el.scrollTop;\n const visibleEnd = visibleStart + this.containerHeight;\n\n if (itemTop >= visibleStart && itemTop + itemHeight <= visibleEnd) return;\n\n targetScrollTop = itemTop < visibleStart ? itemTop : itemTop + itemHeight - this.containerHeight;\n }\n\n el.scrollTo({ behavior, top: this.clampScrollTop(targetScrollTop) });\n }\n\n /** Programmatically scroll to a specific pixel offset. */\n scrollToOffset(offset: number, options: { behavior?: ScrollBehavior } = {}): void {\n this.attachedEl?.scrollTo({\n behavior: options.behavior ?? 'auto',\n top: this.clampScrollTop(offset),\n });\n }\n\n /**\n * Invalidate all item measurements. Call after a font load or layout shift\n * that changes item heights.\n */\n invalidate(): void {\n this.measuredHeights.clear();\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n // ─── Private Helpers ──────────────────────────────────────────────────────\n\n private teardown(): void {\n if (this.scrollHandler && this.attachedEl) {\n this.attachedEl.removeEventListener('scroll', this.scrollHandler);\n this.scrollHandler = null;\n }\n\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.attachedEl = null;\n }\n\n private heightAt(index: number): number {\n return this.measuredHeights.get(index) ?? this._estimateSizeFn(index);\n }\n\n private offsetAt(index: number): number {\n return this.scrollOffsets[index] ?? 0;\n }\n\n private clampScrollTop(offset: number): number {\n const safeOffset = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, this.totalSize - this.containerHeight);\n\n return Math.min(maxOffset, Math.max(0, safeOffset));\n }\n\n private buildOffsets(): void {\n // Invalidate the render range cache: item positions may shift even when the\n // visible index range stays the same (e.g. an item above grew taller).\n this.prevRenderStart = -1;\n this.prevRenderEnd = -1;\n\n const offsets = new Float64Array(this._count + 1);\n\n offsets[0] = 0;\n for (let i = 0; i < this._count; i++) {\n offsets[i + 1] = offsets[i] + this.heightAt(i);\n }\n this.scrollOffsets = offsets;\n this.totalSize = offsets[this._count] ?? 0;\n }\n\n private computeVisible(): void {\n const start = this.scrollTop;\n const end = start + this.containerHeight;\n\n // Binary search for the first visible index\n let lo = 0;\n let hi = this._count - 1;\n\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n\n if (this.scrollOffsets[mid + 1] <= start) lo = mid + 1;\n else hi = mid;\n }\n\n const firstVisible = lo;\n\n // Binary search for the last visible index\n let lo2 = firstVisible;\n let hi2 = this._count - 1;\n\n while (lo2 < hi2) {\n const mid = (lo2 + hi2 + 1) >> 1;\n\n if (this.scrollOffsets[mid] < end) lo2 = mid;\n else hi2 = mid - 1;\n }\n\n const lastVisible = lo2;\n const renderStart = Math.max(0, firstVisible - this.overscan);\n const renderEnd = Math.min(this._count - 1, lastVisible + this.overscan);\n\n // Skip re-render when the range is unchanged (e.g. a sub-pixel scroll that\n // doesn't cross an item boundary). The cache is reset in buildOffsets() so\n // any layout change always produces at least one render.\n if (renderStart === this.prevRenderStart && renderEnd === this.prevRenderEnd) return;\n\n this.prevRenderStart = renderStart;\n this.prevRenderEnd = renderEnd;\n\n const items: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n items.push({ height: this.heightAt(i), index: i, top: this.scrollOffsets[i] });\n }\n\n this.virtualItems = items;\n this.onChange?.(items, this.totalSize);\n }\n}\n\n// ─── Convenience factory ──────────────────────────────────────────────────────\n\n/**\n * Creates and immediately attaches a `Virtualizer` to the given scroll container.\n *\n * @example\n * ```ts\n * import { createVirtualizer } from '@vielzeug/virtualit';\n *\n * const virt = createVirtualizer(scrollContainerEl, {\n * count: items.length,\n * estimateSize: 36,\n * onChange: (virtualItems, totalSize) => {\n * // update your rendered list\n * },\n * });\n *\n * // Later:\n * virt.destroy();\n *\n * // Or, with the Explicit Resource Management proposal:\n * {\n * using virt = createVirtualizer(scrollContainerEl, { ... });\n * } // virt.destroy() called automatically\n * ```\n */\nexport function createVirtualizer(el: HTMLElement, options: VirtualizerOptions): Virtualizer {\n const v = new Virtualizer(options);\n\n v.attach(el);\n\n return v;\n}\n"],"mappings":"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"}
|
package/dist/virtualit.d.ts
CHANGED
package/dist/virtualit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualit.d.ts","sourceRoot":"","sources":["../src/virtualit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IACpD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9D;AAED,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC5C,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"virtualit.d.ts","sourceRoot":"","sources":["../src/virtualit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IACpD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9D;AAED,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC5C,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAgCD,qBAAa,WAAW;IAEtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAkE;IAGlF,OAAO,CAAC,eAAe,CAAkC;IACzD,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,SAAS,CAAK;IAGtB,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,aAAa,CAAM;IAG3B,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,aAAa,CAA6B;IAGlD,OAAO,CAAC,YAAY,CAAS;gBAEjB,OAAO,EAAE,kBAAkB;IAevC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,6EAA6E;IAC7E,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAStB;IAED;;;OAGG;IACH,IAAI,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,EAMxD;IAED,4CAA4C;IAC5C,MAAM,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI;IAwB7B,+CAA+C;IAC/C,OAAO,IAAI,IAAI;IAIf,iEAAiE;IACjE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;IAIxB,mDAAmD;IACnD,eAAe,IAAI,WAAW,EAAE;IAIhC,wEAAwE;IACxE,YAAY,IAAI,MAAM;IAItB;;;;;OAKG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAqBnD,mDAAmD;IACnD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,IAAI;IAiCtE,0DAA0D;IAC1D,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,QAAQ,CAAC,EAAE,cAAc,CAAA;KAAO,GAAG,IAAI;IAOjF;;;OAGG;IACH,UAAU,IAAI,IAAI;IASlB,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,cAAc;CAiDvB;AAID;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAM3F"}
|
package/dist/virtualit.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
//#region src/virtualit.ts
|
|
2
|
-
var e =
|
|
2
|
+
var e = 36, t = 3;
|
|
3
|
+
function n(e, t = 0) {
|
|
4
|
+
return Number.isFinite(e) ? Math.max(0, Math.floor(e)) : t;
|
|
5
|
+
}
|
|
6
|
+
function r(e, t) {
|
|
7
|
+
return !Number.isFinite(e) || e <= 0 ? t : e;
|
|
8
|
+
}
|
|
9
|
+
function i(t, n = e) {
|
|
10
|
+
if (typeof t == "number") {
|
|
11
|
+
let e = r(t, n);
|
|
12
|
+
return () => e;
|
|
13
|
+
}
|
|
14
|
+
return (e) => r(t(e), n);
|
|
15
|
+
}
|
|
16
|
+
var a = class {
|
|
3
17
|
_count;
|
|
4
18
|
_estimateSizeFn;
|
|
5
19
|
overscan;
|
|
@@ -16,19 +30,18 @@ var e = class {
|
|
|
16
30
|
resizeObserver = null;
|
|
17
31
|
scrollHandler = null;
|
|
18
32
|
pendingBuild = !1;
|
|
19
|
-
constructor(
|
|
20
|
-
this._count = e.
|
|
21
|
-
let t = e.estimateSize ?? 36;
|
|
22
|
-
this._estimateSizeFn = typeof t == "number" ? () => t : t, this.overscan = e.overscan ?? 3, this.onChange = e.onChange, this.buildOffsets();
|
|
33
|
+
constructor(r) {
|
|
34
|
+
this._count = n(r.count), this._estimateSizeFn = i(r.estimateSize ?? e), this.overscan = n(r.overscan ?? t, t), this.onChange = r.onChange, this.buildOffsets();
|
|
23
35
|
}
|
|
24
36
|
get count() {
|
|
25
37
|
return this._count;
|
|
26
38
|
}
|
|
27
39
|
set count(e) {
|
|
28
|
-
|
|
40
|
+
let t = n(e);
|
|
41
|
+
t !== this._count && (this._count = t, this.buildOffsets(), this.attachedEl && this.computeVisible());
|
|
29
42
|
}
|
|
30
43
|
set estimateSize(e) {
|
|
31
|
-
this._estimateSizeFn =
|
|
44
|
+
this._estimateSizeFn = i(e), this.measuredHeights.clear(), this.buildOffsets(), this.attachedEl && this.computeVisible();
|
|
32
45
|
}
|
|
33
46
|
attach(e) {
|
|
34
47
|
this.teardown(), this.attachedEl = e, this.containerHeight = e.clientHeight, this.scrollTop = e.scrollTop, this.scrollHandler = () => {
|
|
@@ -50,14 +63,15 @@ var e = class {
|
|
|
50
63
|
return this.totalSize;
|
|
51
64
|
}
|
|
52
65
|
measureElement(e, t) {
|
|
53
|
-
|
|
66
|
+
let i = n(e, -1), a = r(t, -1);
|
|
67
|
+
i < 0 || i >= this._count || a <= 0 || this.heightAt(i) !== a && (this.measuredHeights.set(i, a), this.pendingBuild || (this.pendingBuild = !0, queueMicrotask(() => {
|
|
54
68
|
this.pendingBuild = !1, this.buildOffsets(), this.attachedEl && this.computeVisible();
|
|
55
69
|
})));
|
|
56
70
|
}
|
|
57
71
|
scrollToIndex(e, t = {}) {
|
|
58
72
|
let n = this.attachedEl;
|
|
59
|
-
if (!n) return;
|
|
60
|
-
let r = Math.max(0, Math.min(e, this._count - 1)), i = t.align ?? "auto", a = t.behavior ?? "auto", o = this.offsetAt(r), s = this.heightAt(r), c;
|
|
73
|
+
if (!n || this._count <= 0) return;
|
|
74
|
+
let r = Math.max(0, Math.min(Number.isFinite(e) ? Math.floor(e) : 0, this._count - 1)), i = t.align ?? "auto", a = t.behavior ?? "auto", o = this.offsetAt(r), s = this.heightAt(r), c;
|
|
61
75
|
if (i === "start") c = o;
|
|
62
76
|
else if (i === "end") c = o + s - this.containerHeight;
|
|
63
77
|
else if (i === "center") c = o - (this.containerHeight - s) / 2;
|
|
@@ -68,13 +82,13 @@ var e = class {
|
|
|
68
82
|
}
|
|
69
83
|
n.scrollTo({
|
|
70
84
|
behavior: a,
|
|
71
|
-
top:
|
|
85
|
+
top: this.clampScrollTop(c)
|
|
72
86
|
});
|
|
73
87
|
}
|
|
74
88
|
scrollToOffset(e, t = {}) {
|
|
75
89
|
this.attachedEl?.scrollTo({
|
|
76
90
|
behavior: t.behavior ?? "auto",
|
|
77
|
-
top:
|
|
91
|
+
top: this.clampScrollTop(e)
|
|
78
92
|
});
|
|
79
93
|
}
|
|
80
94
|
invalidate() {
|
|
@@ -89,6 +103,10 @@ var e = class {
|
|
|
89
103
|
offsetAt(e) {
|
|
90
104
|
return this.scrollOffsets[e] ?? 0;
|
|
91
105
|
}
|
|
106
|
+
clampScrollTop(e) {
|
|
107
|
+
let t = Number.isFinite(e) ? e : 0, n = Math.max(0, this.totalSize - this.containerHeight);
|
|
108
|
+
return Math.min(n, Math.max(0, t));
|
|
109
|
+
}
|
|
92
110
|
buildOffsets() {
|
|
93
111
|
this.prevRenderStart = -1, this.prevRenderEnd = -1;
|
|
94
112
|
let e = new Float64Array(this._count + 1);
|
|
@@ -119,11 +137,11 @@ var e = class {
|
|
|
119
137
|
this.virtualItems = u, this.onChange?.(u, this.totalSize);
|
|
120
138
|
}
|
|
121
139
|
};
|
|
122
|
-
function
|
|
123
|
-
let
|
|
124
|
-
return
|
|
140
|
+
function o(e, t) {
|
|
141
|
+
let n = new a(t);
|
|
142
|
+
return n.attach(e), n;
|
|
125
143
|
}
|
|
126
144
|
//#endregion
|
|
127
|
-
export {
|
|
145
|
+
export { a as Virtualizer, o as createVirtualizer };
|
|
128
146
|
|
|
129
147
|
//# sourceMappingURL=virtualit.js.map
|
package/dist/virtualit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualit.js","names":[],"sources":["../src/virtualit.ts"],"sourcesContent":["/**\n * @vielzeug/virtualit — Lightweight virtual list / infinite-scroll engine.\n *\n * Framework-agnostic: works with any DOM rendering layer.\n * Uses a `ResizeObserver` to re-measure the scroll container and a\n * `scroll` listener to update the visible window.\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualItem {\n /** Original item index in the full list */\n index: number;\n /** Pixel offset from the top of the virtual scroll area */\n top: number;\n /** Measured (or estimated) pixel height for this item */\n height: number;\n}\n\nexport interface VirtualizerOptions {\n /** Total number of items. */\n count: number;\n /**\n * Either a fixed row height or a per-index estimator function.\n * Defaults to 36px.\n */\n estimateSize?: number | ((index: number) => number);\n /**\n * Number of items to render outside the visible viewport on each side.\n * Higher values reduce blank-flash during fast scroll at the cost of more DOM nodes.\n * Defaults to 3.\n */\n overscan?: number;\n /**\n * Called whenever the visible range changes. Trigger your re-render here.\n */\n onChange?: (items: VirtualItem[], totalSize: number) => void;\n}\n\nexport interface ScrollToIndexOptions {\n /** 'start' | 'end' | 'center' | 'auto'. Defaults to 'auto'. */\n align?: 'start' | 'end' | 'center' | 'auto';\n /** Scroll behaviour. Defaults to 'auto'. */\n behavior?: ScrollBehavior;\n}\n\n// ─── Virtualizer ──────────────────────────────────────────────────────────────\n\nexport class Virtualizer {\n // mutable options\n private _count: number;\n private _estimateSizeFn: (index: number) => number;\n private overscan: number;\n private onChange: ((items: VirtualItem[], totalSize: number) => void) | undefined;\n\n // internal state\n private measuredHeights: Map<number, number> = new Map();\n private virtualItems: VirtualItem[] = [];\n private totalSize = 0;\n private scrollOffsets: Float64Array = new Float64Array(0); // prefix-sum cache\n private containerHeight = 0;\n private scrollTop = 0;\n\n // render range cache — reset in buildOffsets() so layout changes always re-render\n private prevRenderStart = -1;\n private prevRenderEnd = -1;\n\n // cleanup handles\n private attachedEl: HTMLElement | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private scrollHandler: (() => void) | null = null;\n\n // batching flag for measureElement\n private pendingBuild = false;\n\n constructor(options: VirtualizerOptions) {\n this._count = options.count;\n\n const est = options.estimateSize ?? 36;\n\n this._estimateSizeFn = typeof est === 'number' ? () => est : est;\n this.overscan = options.overscan ?? 3;\n this.onChange = options.onChange;\n // Build the offset table eagerly; computeVisible is deferred to attach()\n // so the first onChange call always has a real containerHeight.\n this.buildOffsets();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────\n\n get count(): number {\n return this._count;\n }\n\n /** Setting count automatically rebuilds offsets and triggers a re-render. */\n set count(value: number) {\n this._count = value;\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n /**\n * Update the size estimator. Clears all measured heights and re-renders.\n * Useful when switching between row density modes (e.g. compact ↔ comfortable).\n */\n set estimateSize(fn: number | ((index: number) => number)) {\n this._estimateSizeFn = typeof fn === 'number' ? () => fn : fn;\n this.measuredHeights.clear();\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n /** Start observing the scroll container. */\n attach(el: HTMLElement): void {\n this.teardown();\n\n this.attachedEl = el;\n this.containerHeight = el.clientHeight;\n this.scrollTop = el.scrollTop;\n\n this.scrollHandler = () => {\n this.scrollTop = el.scrollTop;\n this.computeVisible();\n };\n el.addEventListener('scroll', this.scrollHandler, { passive: true });\n\n this.resizeObserver = new ResizeObserver(() => {\n this.containerHeight = el.clientHeight;\n // The offset table depends only on item heights, not container height —\n // no need to rebuild it here, only recompute the visible window.\n this.computeVisible();\n });\n this.resizeObserver.observe(el);\n\n this.computeVisible();\n }\n\n /** Stop observing and remove all listeners. */\n destroy(): void {\n this.teardown();\n }\n\n /** Supports the Explicit Resource Management `using` keyword. */\n [Symbol.dispose](): void {\n this.destroy();\n }\n\n /** Returns the currently visible virtual items. */\n getVirtualItems(): VirtualItem[] {\n return this.virtualItems;\n }\n\n /** Total pixel height of the entire list (set as the spacer height). */\n getTotalSize(): number {\n return this.totalSize;\n }\n\n /**\n * Record a measured height for a rendered item (for variable-height lists).\n *\n * Measurements are batched via microtask — safe to call for every item in a\n * render loop without incurring O(n²) rebuilds.\n */\n measureElement(index: number, height: number): void {\n if (this.heightAt(index) === height) return;\n\n this.measuredHeights.set(index, height);\n\n if (!this.pendingBuild) {\n this.pendingBuild = true;\n queueMicrotask(() => {\n this.pendingBuild = false;\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n });\n }\n }\n\n /** Programmatically scroll to a specific index. */\n scrollToIndex(index: number, options: ScrollToIndexOptions = {}): void {\n const el = this.attachedEl;\n\n if (!el) return;\n\n const clampedIndex = Math.max(0, Math.min(index, this._count - 1));\n const align = options.align ?? 'auto';\n const behavior = options.behavior ?? 'auto';\n const itemTop = this.offsetAt(clampedIndex);\n const itemHeight = this.heightAt(clampedIndex);\n\n let targetScrollTop: number;\n\n if (align === 'start') {\n targetScrollTop = itemTop;\n } else if (align === 'end') {\n targetScrollTop = itemTop + itemHeight - this.containerHeight;\n } else if (align === 'center') {\n targetScrollTop = itemTop - (this.containerHeight - itemHeight) / 2;\n } else {\n // auto: scroll only if not already visible\n const visibleStart = el.scrollTop;\n const visibleEnd = visibleStart + this.containerHeight;\n\n if (itemTop >= visibleStart && itemTop + itemHeight <= visibleEnd) return;\n\n targetScrollTop = itemTop < visibleStart ? itemTop : itemTop + itemHeight - this.containerHeight;\n }\n\n el.scrollTo({ behavior, top: Math.max(0, targetScrollTop) });\n }\n\n /** Programmatically scroll to a specific pixel offset. */\n scrollToOffset(offset: number, options: { behavior?: ScrollBehavior } = {}): void {\n this.attachedEl?.scrollTo({ behavior: options.behavior ?? 'auto', top: Math.max(0, offset) });\n }\n\n /**\n * Invalidate all item measurements. Call after a font load or layout shift\n * that changes item heights.\n */\n invalidate(): void {\n this.measuredHeights.clear();\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n // ─── Private Helpers ──────────────────────────────────────────────────────\n\n private teardown(): void {\n if (this.scrollHandler && this.attachedEl) {\n this.attachedEl.removeEventListener('scroll', this.scrollHandler);\n this.scrollHandler = null;\n }\n\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.attachedEl = null;\n }\n\n private heightAt(index: number): number {\n return this.measuredHeights.get(index) ?? this._estimateSizeFn(index);\n }\n\n private offsetAt(index: number): number {\n return this.scrollOffsets[index] ?? 0;\n }\n\n private buildOffsets(): void {\n // Invalidate the render range cache: item positions may shift even when the\n // visible index range stays the same (e.g. an item above grew taller).\n this.prevRenderStart = -1;\n this.prevRenderEnd = -1;\n\n const offsets = new Float64Array(this._count + 1);\n\n offsets[0] = 0;\n for (let i = 0; i < this._count; i++) {\n offsets[i + 1] = offsets[i] + this.heightAt(i);\n }\n this.scrollOffsets = offsets;\n this.totalSize = offsets[this._count] ?? 0;\n }\n\n private computeVisible(): void {\n const start = this.scrollTop;\n const end = start + this.containerHeight;\n\n // Binary search for the first visible index\n let lo = 0;\n let hi = this._count - 1;\n\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n\n if (this.scrollOffsets[mid + 1] <= start) lo = mid + 1;\n else hi = mid;\n }\n\n const firstVisible = lo;\n\n // Binary search for the last visible index\n let lo2 = firstVisible;\n let hi2 = this._count - 1;\n\n while (lo2 < hi2) {\n const mid = (lo2 + hi2 + 1) >> 1;\n\n if (this.scrollOffsets[mid] < end) lo2 = mid;\n else hi2 = mid - 1;\n }\n\n const lastVisible = lo2;\n const renderStart = Math.max(0, firstVisible - this.overscan);\n const renderEnd = Math.min(this._count - 1, lastVisible + this.overscan);\n\n // Skip re-render when the range is unchanged (e.g. a sub-pixel scroll that\n // doesn't cross an item boundary). The cache is reset in buildOffsets() so\n // any layout change always produces at least one render.\n if (renderStart === this.prevRenderStart && renderEnd === this.prevRenderEnd) return;\n\n this.prevRenderStart = renderStart;\n this.prevRenderEnd = renderEnd;\n\n const items: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n items.push({ height: this.heightAt(i), index: i, top: this.scrollOffsets[i] });\n }\n\n this.virtualItems = items;\n this.onChange?.(items, this.totalSize);\n }\n}\n\n// ─── Convenience factory ──────────────────────────────────────────────────────\n\n/**\n * Creates and immediately attaches a `Virtualizer` to the given scroll container.\n *\n * @example\n * ```ts\n * import { createVirtualizer } from '@vielzeug/virtualit';\n *\n * const virt = createVirtualizer(scrollContainerEl, {\n * count: items.length,\n * estimateSize: 36,\n * onChange: (virtualItems, totalSize) => {\n * // update your rendered list\n * },\n * });\n *\n * // Later:\n * virt.destroy();\n *\n * // Or, with the Explicit Resource Management proposal:\n * {\n * using virt = createVirtualizer(scrollContainerEl, { ... });\n * } // virt.destroy() called automatically\n * ```\n */\nexport function createVirtualizer(el: HTMLElement, options: VirtualizerOptions): Virtualizer {\n const v = new Virtualizer(options);\n\n v.attach(el);\n\n return v;\n}\n"],"mappings":";AAgDA,IAAa,IAAb,MAAyB;CAEvB;CACA;CACA;CACA;CAGA,kCAA+C,IAAI,KAAK;CACxD,eAAsC,EAAE;CACxC,YAAoB;CACpB,gBAAsC,IAAI,cAAe;CACzD,kBAA0B;CAC1B,YAAoB;CAGpB,kBAA0B;CAC1B,gBAAwB;CAGxB,aAAyC;CACzC,iBAAgD;CAChD,gBAA6C;CAG7C,eAAuB;CAEvB,YAAY,GAA6B;AACvC,OAAK,SAAS,EAAQ;EAEtB,IAAM,IAAM,EAAQ,gBAAgB;AAOpC,EALA,KAAK,kBAAkB,OAAO,KAAQ,iBAAiB,IAAM,GAC7D,KAAK,WAAW,EAAQ,YAAY,GACpC,KAAK,WAAW,EAAQ,UAGxB,KAAK,cAAc;;CAKrB,IAAI,QAAgB;AAClB,SAAO,KAAK;;CAId,IAAI,MAAM,GAAe;AAIvB,EAHA,KAAK,SAAS,GACd,KAAK,cAAc,EAEf,KAAK,cAAY,KAAK,gBAAgB;;CAO5C,IAAI,aAAa,GAA0C;AAKzD,EAJA,KAAK,kBAAkB,OAAO,KAAO,iBAAiB,IAAK,GAC3D,KAAK,gBAAgB,OAAO,EAC5B,KAAK,cAAc,EAEf,KAAK,cAAY,KAAK,gBAAgB;;CAI5C,OAAO,GAAuB;AAqB5B,EApBA,KAAK,UAAU,EAEf,KAAK,aAAa,GAClB,KAAK,kBAAkB,EAAG,cAC1B,KAAK,YAAY,EAAG,WAEpB,KAAK,sBAAsB;AAEzB,GADA,KAAK,YAAY,EAAG,WACpB,KAAK,gBAAgB;KAEvB,EAAG,iBAAiB,UAAU,KAAK,eAAe,EAAE,SAAS,IAAM,CAAC,EAEpE,KAAK,iBAAiB,IAAI,qBAAqB;AAI7C,GAHA,KAAK,kBAAkB,EAAG,cAG1B,KAAK,gBAAgB;IACrB,EACF,KAAK,eAAe,QAAQ,EAAG,EAE/B,KAAK,gBAAgB;;CAIvB,UAAgB;AACd,OAAK,UAAU;;CAIjB,CAAC,OAAO,WAAiB;AACvB,OAAK,SAAS;;CAIhB,kBAAiC;AAC/B,SAAO,KAAK;;CAId,eAAuB;AACrB,SAAO,KAAK;;CASd,eAAe,GAAe,GAAsB;AAC9C,OAAK,SAAS,EAAM,KAAK,MAE7B,KAAK,gBAAgB,IAAI,GAAO,EAAO,EAElC,KAAK,iBACR,KAAK,eAAe,IACpB,qBAAqB;AAInB,GAHA,KAAK,eAAe,IACpB,KAAK,cAAc,EAEf,KAAK,cAAY,KAAK,gBAAgB;IAC1C;;CAKN,cAAc,GAAe,IAAgC,EAAE,EAAQ;EACrE,IAAM,IAAK,KAAK;AAEhB,MAAI,CAAC,EAAI;EAET,IAAM,IAAe,KAAK,IAAI,GAAG,KAAK,IAAI,GAAO,KAAK,SAAS,EAAE,CAAC,EAC5D,IAAQ,EAAQ,SAAS,QACzB,IAAW,EAAQ,YAAY,QAC/B,IAAU,KAAK,SAAS,EAAa,EACrC,IAAa,KAAK,SAAS,EAAa,EAE1C;AAEJ,MAAI,MAAU,QACZ,KAAkB;WACT,MAAU,MACnB,KAAkB,IAAU,IAAa,KAAK;WACrC,MAAU,SACnB,KAAkB,KAAW,KAAK,kBAAkB,KAAc;OAC7D;GAEL,IAAM,IAAe,EAAG,WAClB,IAAa,IAAe,KAAK;AAEvC,OAAI,KAAW,KAAgB,IAAU,KAAc,EAAY;AAEnE,OAAkB,IAAU,IAAe,IAAU,IAAU,IAAa,KAAK;;AAGnF,IAAG,SAAS;GAAE;GAAU,KAAK,KAAK,IAAI,GAAG,EAAgB;GAAE,CAAC;;CAI9D,eAAe,GAAgB,IAAyC,EAAE,EAAQ;AAChF,OAAK,YAAY,SAAS;GAAE,UAAU,EAAQ,YAAY;GAAQ,KAAK,KAAK,IAAI,GAAG,EAAO;GAAE,CAAC;;CAO/F,aAAmB;AAIjB,EAHA,KAAK,gBAAgB,OAAO,EAC5B,KAAK,cAAc,EAEf,KAAK,cAAY,KAAK,gBAAgB;;CAK5C,WAAyB;AAQvB,EAPI,KAAK,iBAAiB,KAAK,eAC7B,KAAK,WAAW,oBAAoB,UAAU,KAAK,cAAc,EACjE,KAAK,gBAAgB,OAGvB,KAAK,gBAAgB,YAAY,EACjC,KAAK,iBAAiB,MACtB,KAAK,aAAa;;CAGpB,SAAiB,GAAuB;AACtC,SAAO,KAAK,gBAAgB,IAAI,EAAM,IAAI,KAAK,gBAAgB,EAAM;;CAGvE,SAAiB,GAAuB;AACtC,SAAO,KAAK,cAAc,MAAU;;CAGtC,eAA6B;AAI3B,EADA,KAAK,kBAAkB,IACvB,KAAK,gBAAgB;EAErB,IAAM,IAAU,IAAI,aAAa,KAAK,SAAS,EAAE;AAEjD,IAAQ,KAAK;AACb,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAC/B,GAAQ,IAAI,KAAK,EAAQ,KAAK,KAAK,SAAS,EAAE;AAGhD,EADA,KAAK,gBAAgB,GACrB,KAAK,YAAY,EAAQ,KAAK,WAAW;;CAG3C,iBAA+B;EAC7B,IAAM,IAAQ,KAAK,WACb,IAAM,IAAQ,KAAK,iBAGrB,IAAK,GACL,IAAK,KAAK,SAAS;AAEvB,SAAO,IAAK,IAAI;GACd,IAAM,IAAO,IAAK,KAAO;AAEzB,GAAI,KAAK,cAAc,IAAM,MAAM,IAAO,IAAK,IAAM,IAChD,IAAK;;EAGZ,IAAM,IAAe,GAGjB,IAAM,GACN,IAAM,KAAK,SAAS;AAExB,SAAO,IAAM,IAAK;GAChB,IAAM,IAAO,IAAM,IAAM,KAAM;AAE/B,GAAI,KAAK,cAAc,KAAO,IAAK,IAAM,IACpC,IAAM,IAAM;;EAGnB,IAAM,IAAc,GACd,IAAc,KAAK,IAAI,GAAG,IAAe,KAAK,SAAS,EACvD,IAAY,KAAK,IAAI,KAAK,SAAS,GAAG,IAAc,KAAK,SAAS;AAKxE,MAAI,MAAgB,KAAK,mBAAmB,MAAc,KAAK,cAAe;AAG9E,EADA,KAAK,kBAAkB,GACvB,KAAK,gBAAgB;EAErB,IAAM,IAAuB,EAAE;AAE/B,OAAK,IAAI,IAAI,GAAa,KAAK,GAAW,IACxC,GAAM,KAAK;GAAE,QAAQ,KAAK,SAAS,EAAE;GAAE,OAAO;GAAG,KAAK,KAAK,cAAc;GAAI,CAAC;AAIhF,EADA,KAAK,eAAe,GACpB,KAAK,WAAW,GAAO,KAAK,UAAU;;;AA8B1C,SAAgB,EAAkB,GAAiB,GAA0C;CAC3F,IAAM,IAAI,IAAI,EAAY,EAAQ;AAIlC,QAFA,EAAE,OAAO,EAAG,EAEL"}
|
|
1
|
+
{"version":3,"file":"virtualit.js","names":[],"sources":["../src/virtualit.ts"],"sourcesContent":["/**\n * @vielzeug/virtualit — Lightweight virtual list / infinite-scroll engine.\n *\n * Framework-agnostic: works with any DOM rendering layer.\n * Uses a `ResizeObserver` to re-measure the scroll container and a\n * `scroll` listener to update the visible window.\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualItem {\n /** Original item index in the full list */\n index: number;\n /** Pixel offset from the top of the virtual scroll area */\n top: number;\n /** Measured (or estimated) pixel height for this item */\n height: number;\n}\n\nexport interface VirtualizerOptions {\n /** Total number of items. */\n count: number;\n /**\n * Either a fixed row height or a per-index estimator function.\n * Defaults to 36px.\n */\n estimateSize?: number | ((index: number) => number);\n /**\n * Number of items to render outside the visible viewport on each side.\n * Higher values reduce blank-flash during fast scroll at the cost of more DOM nodes.\n * Defaults to 3.\n */\n overscan?: number;\n /**\n * Called whenever the visible range changes. Trigger your re-render here.\n */\n onChange?: (items: VirtualItem[], totalSize: number) => void;\n}\n\nexport interface ScrollToIndexOptions {\n /** 'start' | 'end' | 'center' | 'auto'. Defaults to 'auto'. */\n align?: 'start' | 'end' | 'center' | 'auto';\n /** Scroll behaviour. Defaults to 'auto'. */\n behavior?: ScrollBehavior;\n}\n\nconst DEFAULT_ESTIMATE_SIZE = 36;\nconst DEFAULT_OVERSCAN = 3;\n\nfunction toNonNegativeInt(value: number, fallback = 0): number {\n if (!Number.isFinite(value)) return fallback;\n\n return Math.max(0, Math.floor(value));\n}\n\nfunction toPositiveNumber(value: number, fallback: number): number {\n if (!Number.isFinite(value) || value <= 0) return fallback;\n\n return value;\n}\n\nfunction createEstimateSizeFn(\n estimate: number | ((index: number) => number),\n fallback = DEFAULT_ESTIMATE_SIZE,\n): (index: number) => number {\n if (typeof estimate === 'number') {\n const size = toPositiveNumber(estimate, fallback);\n\n return () => size;\n }\n\n return (index: number) => toPositiveNumber(estimate(index), fallback);\n}\n\n// ─── Virtualizer ──────────────────────────────────────────────────────────────\n\nexport class Virtualizer {\n // mutable options\n private _count: number;\n private _estimateSizeFn: (index: number) => number;\n private overscan: number;\n private onChange: ((items: VirtualItem[], totalSize: number) => void) | undefined;\n\n // internal state\n private measuredHeights: Map<number, number> = new Map();\n private virtualItems: VirtualItem[] = [];\n private totalSize = 0;\n private scrollOffsets: Float64Array = new Float64Array(0); // prefix-sum cache\n private containerHeight = 0;\n private scrollTop = 0;\n\n // render range cache — reset in buildOffsets() so layout changes always re-render\n private prevRenderStart = -1;\n private prevRenderEnd = -1;\n\n // cleanup handles\n private attachedEl: HTMLElement | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private scrollHandler: (() => void) | null = null;\n\n // batching flag for measureElement\n private pendingBuild = false;\n\n constructor(options: VirtualizerOptions) {\n this._count = toNonNegativeInt(options.count);\n\n const est = options.estimateSize ?? DEFAULT_ESTIMATE_SIZE;\n\n this._estimateSizeFn = createEstimateSizeFn(est);\n this.overscan = toNonNegativeInt(options.overscan ?? DEFAULT_OVERSCAN, DEFAULT_OVERSCAN);\n this.onChange = options.onChange;\n // Build the offset table eagerly; computeVisible is deferred to attach()\n // so the first onChange call always has a real containerHeight.\n this.buildOffsets();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────\n\n get count(): number {\n return this._count;\n }\n\n /** Setting count automatically rebuilds offsets and triggers a re-render. */\n set count(value: number) {\n const nextCount = toNonNegativeInt(value);\n\n if (nextCount === this._count) return;\n\n this._count = nextCount;\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n /**\n * Update the size estimator. Clears all measured heights and re-renders.\n * Useful when switching between row density modes (e.g. compact ↔ comfortable).\n */\n set estimateSize(fn: number | ((index: number) => number)) {\n this._estimateSizeFn = createEstimateSizeFn(fn);\n this.measuredHeights.clear();\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n /** Start observing the scroll container. */\n attach(el: HTMLElement): void {\n this.teardown();\n\n this.attachedEl = el;\n this.containerHeight = el.clientHeight;\n this.scrollTop = el.scrollTop;\n\n this.scrollHandler = () => {\n this.scrollTop = el.scrollTop;\n this.computeVisible();\n };\n el.addEventListener('scroll', this.scrollHandler, { passive: true });\n\n this.resizeObserver = new ResizeObserver(() => {\n this.containerHeight = el.clientHeight;\n // The offset table depends only on item heights, not container height —\n // no need to rebuild it here, only recompute the visible window.\n this.computeVisible();\n });\n this.resizeObserver.observe(el);\n\n this.computeVisible();\n }\n\n /** Stop observing and remove all listeners. */\n destroy(): void {\n this.teardown();\n }\n\n /** Supports the Explicit Resource Management `using` keyword. */\n [Symbol.dispose](): void {\n this.destroy();\n }\n\n /** Returns the currently visible virtual items. */\n getVirtualItems(): VirtualItem[] {\n return this.virtualItems;\n }\n\n /** Total pixel height of the entire list (set as the spacer height). */\n getTotalSize(): number {\n return this.totalSize;\n }\n\n /**\n * Record a measured height for a rendered item (for variable-height lists).\n *\n * Measurements are batched via microtask — safe to call for every item in a\n * render loop without incurring O(n²) rebuilds.\n */\n measureElement(index: number, height: number): void {\n const safeIndex = toNonNegativeInt(index, -1);\n const safeHeight = toPositiveNumber(height, -1);\n\n if (safeIndex < 0 || safeIndex >= this._count || safeHeight <= 0) return;\n\n if (this.heightAt(safeIndex) === safeHeight) return;\n\n this.measuredHeights.set(safeIndex, safeHeight);\n\n if (!this.pendingBuild) {\n this.pendingBuild = true;\n queueMicrotask(() => {\n this.pendingBuild = false;\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n });\n }\n }\n\n /** Programmatically scroll to a specific index. */\n scrollToIndex(index: number, options: ScrollToIndexOptions = {}): void {\n const el = this.attachedEl;\n\n if (!el || this._count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, this._count - 1));\n const align = options.align ?? 'auto';\n const behavior = options.behavior ?? 'auto';\n const itemTop = this.offsetAt(clampedIndex);\n const itemHeight = this.heightAt(clampedIndex);\n\n let targetScrollTop: number;\n\n if (align === 'start') {\n targetScrollTop = itemTop;\n } else if (align === 'end') {\n targetScrollTop = itemTop + itemHeight - this.containerHeight;\n } else if (align === 'center') {\n targetScrollTop = itemTop - (this.containerHeight - itemHeight) / 2;\n } else {\n // auto: scroll only if not already visible\n const visibleStart = el.scrollTop;\n const visibleEnd = visibleStart + this.containerHeight;\n\n if (itemTop >= visibleStart && itemTop + itemHeight <= visibleEnd) return;\n\n targetScrollTop = itemTop < visibleStart ? itemTop : itemTop + itemHeight - this.containerHeight;\n }\n\n el.scrollTo({ behavior, top: this.clampScrollTop(targetScrollTop) });\n }\n\n /** Programmatically scroll to a specific pixel offset. */\n scrollToOffset(offset: number, options: { behavior?: ScrollBehavior } = {}): void {\n this.attachedEl?.scrollTo({\n behavior: options.behavior ?? 'auto',\n top: this.clampScrollTop(offset),\n });\n }\n\n /**\n * Invalidate all item measurements. Call after a font load or layout shift\n * that changes item heights.\n */\n invalidate(): void {\n this.measuredHeights.clear();\n this.buildOffsets();\n\n if (this.attachedEl) this.computeVisible();\n }\n\n // ─── Private Helpers ──────────────────────────────────────────────────────\n\n private teardown(): void {\n if (this.scrollHandler && this.attachedEl) {\n this.attachedEl.removeEventListener('scroll', this.scrollHandler);\n this.scrollHandler = null;\n }\n\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.attachedEl = null;\n }\n\n private heightAt(index: number): number {\n return this.measuredHeights.get(index) ?? this._estimateSizeFn(index);\n }\n\n private offsetAt(index: number): number {\n return this.scrollOffsets[index] ?? 0;\n }\n\n private clampScrollTop(offset: number): number {\n const safeOffset = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, this.totalSize - this.containerHeight);\n\n return Math.min(maxOffset, Math.max(0, safeOffset));\n }\n\n private buildOffsets(): void {\n // Invalidate the render range cache: item positions may shift even when the\n // visible index range stays the same (e.g. an item above grew taller).\n this.prevRenderStart = -1;\n this.prevRenderEnd = -1;\n\n const offsets = new Float64Array(this._count + 1);\n\n offsets[0] = 0;\n for (let i = 0; i < this._count; i++) {\n offsets[i + 1] = offsets[i] + this.heightAt(i);\n }\n this.scrollOffsets = offsets;\n this.totalSize = offsets[this._count] ?? 0;\n }\n\n private computeVisible(): void {\n const start = this.scrollTop;\n const end = start + this.containerHeight;\n\n // Binary search for the first visible index\n let lo = 0;\n let hi = this._count - 1;\n\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n\n if (this.scrollOffsets[mid + 1] <= start) lo = mid + 1;\n else hi = mid;\n }\n\n const firstVisible = lo;\n\n // Binary search for the last visible index\n let lo2 = firstVisible;\n let hi2 = this._count - 1;\n\n while (lo2 < hi2) {\n const mid = (lo2 + hi2 + 1) >> 1;\n\n if (this.scrollOffsets[mid] < end) lo2 = mid;\n else hi2 = mid - 1;\n }\n\n const lastVisible = lo2;\n const renderStart = Math.max(0, firstVisible - this.overscan);\n const renderEnd = Math.min(this._count - 1, lastVisible + this.overscan);\n\n // Skip re-render when the range is unchanged (e.g. a sub-pixel scroll that\n // doesn't cross an item boundary). The cache is reset in buildOffsets() so\n // any layout change always produces at least one render.\n if (renderStart === this.prevRenderStart && renderEnd === this.prevRenderEnd) return;\n\n this.prevRenderStart = renderStart;\n this.prevRenderEnd = renderEnd;\n\n const items: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n items.push({ height: this.heightAt(i), index: i, top: this.scrollOffsets[i] });\n }\n\n this.virtualItems = items;\n this.onChange?.(items, this.totalSize);\n }\n}\n\n// ─── Convenience factory ──────────────────────────────────────────────────────\n\n/**\n * Creates and immediately attaches a `Virtualizer` to the given scroll container.\n *\n * @example\n * ```ts\n * import { createVirtualizer } from '@vielzeug/virtualit';\n *\n * const virt = createVirtualizer(scrollContainerEl, {\n * count: items.length,\n * estimateSize: 36,\n * onChange: (virtualItems, totalSize) => {\n * // update your rendered list\n * },\n * });\n *\n * // Later:\n * virt.destroy();\n *\n * // Or, with the Explicit Resource Management proposal:\n * {\n * using virt = createVirtualizer(scrollContainerEl, { ... });\n * } // virt.destroy() called automatically\n * ```\n */\nexport function createVirtualizer(el: HTMLElement, options: VirtualizerOptions): Virtualizer {\n const v = new Virtualizer(options);\n\n v.attach(el);\n\n return v;\n}\n"],"mappings":";AA8CA,IAAM,IAAwB,IACxB,IAAmB;AAEzB,SAAS,EAAiB,GAAe,IAAW,GAAW;AAG7D,QAFK,OAAO,SAAS,EAAM,GAEpB,KAAK,IAAI,GAAG,KAAK,MAAM,EAAM,CAAC,GAFD;;AAKtC,SAAS,EAAiB,GAAe,GAA0B;AAGjE,QAFI,CAAC,OAAO,SAAS,EAAM,IAAI,KAAS,IAAU,IAE3C;;AAGT,SAAS,EACP,GACA,IAAW,GACgB;AAC3B,KAAI,OAAO,KAAa,UAAU;EAChC,IAAM,IAAO,EAAiB,GAAU,EAAS;AAEjD,eAAa;;AAGf,SAAQ,MAAkB,EAAiB,EAAS,EAAM,EAAE,EAAS;;AAKvE,IAAa,IAAb,MAAyB;CAEvB;CACA;CACA;CACA;CAGA,kCAA+C,IAAI,KAAK;CACxD,eAAsC,EAAE;CACxC,YAAoB;CACpB,gBAAsC,IAAI,cAAe;CACzD,kBAA0B;CAC1B,YAAoB;CAGpB,kBAA0B;CAC1B,gBAAwB;CAGxB,aAAyC;CACzC,iBAAgD;CAChD,gBAA6C;CAG7C,eAAuB;CAEvB,YAAY,GAA6B;AAUvC,EATA,KAAK,SAAS,EAAiB,EAAQ,MAAM,EAI7C,KAAK,kBAAkB,EAFX,EAAQ,gBAAgB,EAEY,EAChD,KAAK,WAAW,EAAiB,EAAQ,YAAY,GAAkB,EAAiB,EACxF,KAAK,WAAW,EAAQ,UAGxB,KAAK,cAAc;;CAKrB,IAAI,QAAgB;AAClB,SAAO,KAAK;;CAId,IAAI,MAAM,GAAe;EACvB,IAAM,IAAY,EAAiB,EAAM;AAErC,QAAc,KAAK,WAEvB,KAAK,SAAS,GACd,KAAK,cAAc,EAEf,KAAK,cAAY,KAAK,gBAAgB;;CAO5C,IAAI,aAAa,GAA0C;AAKzD,EAJA,KAAK,kBAAkB,EAAqB,EAAG,EAC/C,KAAK,gBAAgB,OAAO,EAC5B,KAAK,cAAc,EAEf,KAAK,cAAY,KAAK,gBAAgB;;CAI5C,OAAO,GAAuB;AAqB5B,EApBA,KAAK,UAAU,EAEf,KAAK,aAAa,GAClB,KAAK,kBAAkB,EAAG,cAC1B,KAAK,YAAY,EAAG,WAEpB,KAAK,sBAAsB;AAEzB,GADA,KAAK,YAAY,EAAG,WACpB,KAAK,gBAAgB;KAEvB,EAAG,iBAAiB,UAAU,KAAK,eAAe,EAAE,SAAS,IAAM,CAAC,EAEpE,KAAK,iBAAiB,IAAI,qBAAqB;AAI7C,GAHA,KAAK,kBAAkB,EAAG,cAG1B,KAAK,gBAAgB;IACrB,EACF,KAAK,eAAe,QAAQ,EAAG,EAE/B,KAAK,gBAAgB;;CAIvB,UAAgB;AACd,OAAK,UAAU;;CAIjB,CAAC,OAAO,WAAiB;AACvB,OAAK,SAAS;;CAIhB,kBAAiC;AAC/B,SAAO,KAAK;;CAId,eAAuB;AACrB,SAAO,KAAK;;CASd,eAAe,GAAe,GAAsB;EAClD,IAAM,IAAY,EAAiB,GAAO,GAAG,EACvC,IAAa,EAAiB,GAAQ,GAAG;AAE3C,MAAY,KAAK,KAAa,KAAK,UAAU,KAAc,KAE3D,KAAK,SAAS,EAAU,KAAK,MAEjC,KAAK,gBAAgB,IAAI,GAAW,EAAW,EAE1C,KAAK,iBACR,KAAK,eAAe,IACpB,qBAAqB;AAInB,GAHA,KAAK,eAAe,IACpB,KAAK,cAAc,EAEf,KAAK,cAAY,KAAK,gBAAgB;IAC1C;;CAKN,cAAc,GAAe,IAAgC,EAAE,EAAQ;EACrE,IAAM,IAAK,KAAK;AAEhB,MAAI,CAAC,KAAM,KAAK,UAAU,EAAG;EAG7B,IAAM,IAAe,KAAK,IAAI,GAAG,KAAK,IADpB,OAAO,SAAS,EAAM,GAAG,KAAK,MAAM,EAAM,GAAG,GACV,KAAK,SAAS,EAAE,CAAC,EAChE,IAAQ,EAAQ,SAAS,QACzB,IAAW,EAAQ,YAAY,QAC/B,IAAU,KAAK,SAAS,EAAa,EACrC,IAAa,KAAK,SAAS,EAAa,EAE1C;AAEJ,MAAI,MAAU,QACZ,KAAkB;WACT,MAAU,MACnB,KAAkB,IAAU,IAAa,KAAK;WACrC,MAAU,SACnB,KAAkB,KAAW,KAAK,kBAAkB,KAAc;OAC7D;GAEL,IAAM,IAAe,EAAG,WAClB,IAAa,IAAe,KAAK;AAEvC,OAAI,KAAW,KAAgB,IAAU,KAAc,EAAY;AAEnE,OAAkB,IAAU,IAAe,IAAU,IAAU,IAAa,KAAK;;AAGnF,IAAG,SAAS;GAAE;GAAU,KAAK,KAAK,eAAe,EAAgB;GAAE,CAAC;;CAItE,eAAe,GAAgB,IAAyC,EAAE,EAAQ;AAChF,OAAK,YAAY,SAAS;GACxB,UAAU,EAAQ,YAAY;GAC9B,KAAK,KAAK,eAAe,EAAO;GACjC,CAAC;;CAOJ,aAAmB;AAIjB,EAHA,KAAK,gBAAgB,OAAO,EAC5B,KAAK,cAAc,EAEf,KAAK,cAAY,KAAK,gBAAgB;;CAK5C,WAAyB;AAQvB,EAPI,KAAK,iBAAiB,KAAK,eAC7B,KAAK,WAAW,oBAAoB,UAAU,KAAK,cAAc,EACjE,KAAK,gBAAgB,OAGvB,KAAK,gBAAgB,YAAY,EACjC,KAAK,iBAAiB,MACtB,KAAK,aAAa;;CAGpB,SAAiB,GAAuB;AACtC,SAAO,KAAK,gBAAgB,IAAI,EAAM,IAAI,KAAK,gBAAgB,EAAM;;CAGvE,SAAiB,GAAuB;AACtC,SAAO,KAAK,cAAc,MAAU;;CAGtC,eAAuB,GAAwB;EAC7C,IAAM,IAAa,OAAO,SAAS,EAAO,GAAG,IAAS,GAChD,IAAY,KAAK,IAAI,GAAG,KAAK,YAAY,KAAK,gBAAgB;AAEpE,SAAO,KAAK,IAAI,GAAW,KAAK,IAAI,GAAG,EAAW,CAAC;;CAGrD,eAA6B;AAI3B,EADA,KAAK,kBAAkB,IACvB,KAAK,gBAAgB;EAErB,IAAM,IAAU,IAAI,aAAa,KAAK,SAAS,EAAE;AAEjD,IAAQ,KAAK;AACb,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAC/B,GAAQ,IAAI,KAAK,EAAQ,KAAK,KAAK,SAAS,EAAE;AAGhD,EADA,KAAK,gBAAgB,GACrB,KAAK,YAAY,EAAQ,KAAK,WAAW;;CAG3C,iBAA+B;EAC7B,IAAM,IAAQ,KAAK,WACb,IAAM,IAAQ,KAAK,iBAGrB,IAAK,GACL,IAAK,KAAK,SAAS;AAEvB,SAAO,IAAK,IAAI;GACd,IAAM,IAAO,IAAK,KAAO;AAEzB,GAAI,KAAK,cAAc,IAAM,MAAM,IAAO,IAAK,IAAM,IAChD,IAAK;;EAGZ,IAAM,IAAe,GAGjB,IAAM,GACN,IAAM,KAAK,SAAS;AAExB,SAAO,IAAM,IAAK;GAChB,IAAM,IAAO,IAAM,IAAM,KAAM;AAE/B,GAAI,KAAK,cAAc,KAAO,IAAK,IAAM,IACpC,IAAM,IAAM;;EAGnB,IAAM,IAAc,GACd,IAAc,KAAK,IAAI,GAAG,IAAe,KAAK,SAAS,EACvD,IAAY,KAAK,IAAI,KAAK,SAAS,GAAG,IAAc,KAAK,SAAS;AAKxE,MAAI,MAAgB,KAAK,mBAAmB,MAAc,KAAK,cAAe;AAG9E,EADA,KAAK,kBAAkB,GACvB,KAAK,gBAAgB;EAErB,IAAM,IAAuB,EAAE;AAE/B,OAAK,IAAI,IAAI,GAAa,KAAK,GAAW,IACxC,GAAM,KAAK;GAAE,QAAQ,KAAK,SAAS,EAAE;GAAE,OAAO;GAAG,KAAK,KAAK,cAAc;GAAI,CAAC;AAIhF,EADA,KAAK,eAAe,GACpB,KAAK,WAAW,GAAO,KAAK,UAAU;;;AA8B1C,SAAgB,EAAkB,GAAiB,GAA0C;CAC3F,IAAM,IAAI,IAAI,EAAY,EAAQ;AAIlC,QAFA,EAAE,OAAO,EAAG,EAEL"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vielzeug/virtualit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^25.5.0",
|
|
40
40
|
"typescript": "~6.0.2",
|
|
41
|
-
"vite": "^8.0.
|
|
42
|
-
"vitest": "^4.1.
|
|
41
|
+
"vite": "^8.0.3",
|
|
42
|
+
"vitest": "^4.1.2"
|
|
43
43
|
}
|
|
44
44
|
}
|