@x-virtual/core 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LiFi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,260 @@
1
+ class S {
2
+ _options = {};
3
+ //配置项
4
+ _keyToIndexObj = {};
5
+ //key-index对照,包含脏数据,不做删除是因为需要On遍历收益不高
6
+ _chunkList = [];
7
+ //分块数据
8
+ _itemSizeMap = /* @__PURE__ */ new Map();
9
+ //高度缓存数据,包含脏数据,不做删除是因为需要On遍历收益不高
10
+ _itemCount = 0;
11
+ //总条数
12
+ constructor(t = {}) {
13
+ const { getKey: e = null, estimatedHeight: n = null, overscan: s = 10, chunkSize: r = 100, gap: i } = t || {};
14
+ if (typeof e != "function") throw new Error("The parameter getKey must be a function");
15
+ if (typeof n != "function") throw new Error("The parameter estimatedHeight must be a function");
16
+ this._options = { getKey: e, estimatedHeight: n, overscan: s, chunkSize: r, gap: Number(i) || 0 };
17
+ }
18
+ _getItemHeight = (t) => this._itemSizeMap.get(t) || 0;
19
+ _getChunkIndex = (t) => Math.floor(t / this._options.chunkSize);
20
+ _getItemTop = (t) => {
21
+ const e = this._keyToIndexObj[t], n = this._chunkList[this._getChunkIndex(e)];
22
+ let s = n?.top || 0;
23
+ return n && (s += n.prefixSums[Math.max(e - n.start, 0)] || 0), s + ((e || 0) + 1) * this._options.gap;
24
+ };
25
+ _setItemHeight = (t, e) => this._itemSizeMap.set(t, e || 0);
26
+ _getItemKey = (t) => this._options?.getKey?.(t) || null;
27
+ _computeChunk(t) {
28
+ let e = 0;
29
+ const n = t.prefixSums;
30
+ for (let s = t.start; s < t.end; s++)
31
+ n[s - t.start] = e, e += this._getItemHeight(this._getItemKey(s));
32
+ t.height = e;
33
+ }
34
+ _rebuildChunkListFrom(t = 0) {
35
+ const e = this._itemCount;
36
+ if (!e) return;
37
+ const { chunkSize: n } = this._options, s = this._getChunkIndex(t);
38
+ this._chunkList = this._chunkList?.slice?.(0, s) || [];
39
+ const r = this._chunkList[this._chunkList.length - 1];
40
+ let i = (r?.top || 0) + (r?.height || 0);
41
+ const o = Math.ceil(e / n) - s;
42
+ for (let h = 0; h < o; h++) {
43
+ const a = (s + h) * n, c = Math.min(a + n, e), _ = { start: a, end: c, top: i, height: 0, prefixSums: new Float32Array(c - a) };
44
+ this._computeChunk(_), this._chunkList.push(_), i += _.height;
45
+ }
46
+ }
47
+ _updateChunkListFrom(t = /* @__PURE__ */ new Set()) {
48
+ const e = this._chunkList, n = Array.from(t || []).sort((i, o) => i - o);
49
+ if (!n.length) return;
50
+ const s = n[0];
51
+ n?.forEach((i) => this._computeChunk(e[i]));
52
+ let r = (e[s]?.top || 0) + (e[s]?.height || 0);
53
+ for (let i = s + 1; i < e.length; i++)
54
+ e[i].top = r, r += e[i].height;
55
+ }
56
+ _binarySearch(t, e) {
57
+ if (e <= 0 || typeof t != "number" || isNaN(t)) return 0;
58
+ let n = 0, s = e - 1;
59
+ for (; n <= s; ) {
60
+ const r = Math.floor((n + s) / 2), i = this._getItemKey(r);
61
+ if (i == null || i === "") {
62
+ s = r - 1;
63
+ continue;
64
+ }
65
+ const o = this._getItemTop(i);
66
+ if (t >= o && t < o + this._getItemHeight(i)) return r;
67
+ t < o ? s = r - 1 : n = r + 1;
68
+ }
69
+ return Math.min(Math.max(0, s), e - 1);
70
+ }
71
+ getItemTop = (t) => this._getItemTop(this._getItemKey(t));
72
+ getTotalHeight() {
73
+ const t = this._getItemKey(this._itemCount - 1);
74
+ return this._getItemTop(t) + this._getItemHeight(t) + this._options.gap;
75
+ }
76
+ reset() {
77
+ this._itemSizeMap.clear(), this._keyToIndexObj = {}, this._chunkList = [], this._itemCount = 0, this._itemsPool = [];
78
+ }
79
+ setItemCount(t) {
80
+ if (!t || t < 0) return this.reset();
81
+ let e = -1;
82
+ const n = this._getItemKey(this._itemCount - 1), r = t > this._itemCount && this._itemCount - 1 == this._keyToIndexObj[n] ? this._itemCount : 0;
83
+ for (let i = r; i < t; i++) {
84
+ const o = this._getItemKey(i);
85
+ o && (e < 0 && this._keyToIndexObj[o] !== i && (e = i), this._keyToIndexObj[o] = i, this._itemSizeMap.has(o) || this._setItemHeight(o, Number(this._options.estimatedHeight?.(i) || 0)));
86
+ }
87
+ this._itemCount = t, e > -1 && this._rebuildChunkListFrom(e);
88
+ }
89
+ batchUpdateHeight(t = []) {
90
+ let e = 0;
91
+ if (!Array.isArray(t)) return e;
92
+ const n = /* @__PURE__ */ new Set();
93
+ return t.forEach((s) => {
94
+ const { index: r, height: i } = s || {};
95
+ if (typeof r != "number" || typeof i != "number" || r < 0) return;
96
+ const o = this._getItemKey(r);
97
+ !o || this._getItemHeight(o) === i || (e += i - this._getItemHeight(o), this._setItemHeight(o, i), n.add(this._getChunkIndex(r)));
98
+ }), n.size > 0 && this._updateChunkListFrom(n), e;
99
+ }
100
+ getVirtualItems(t, e) {
101
+ if (!t) return [];
102
+ const { overscan: n } = this._options, s = this._itemCount, r = this._binarySearch(e, s), i = n || 10;
103
+ let o = Math.max(r - i, 0), h = r, a = 0;
104
+ for (; h < s && a < t; )
105
+ a += this._getItemHeight(this._getItemKey(h)), h++;
106
+ h = Math.min(h + i, s);
107
+ const c = i + i, _ = h - o;
108
+ if (c > _) {
109
+ let l = c - _;
110
+ o == 0 ? h = Math.min(h + l, s) : h == s && (o = Math.max(o - l, 0));
111
+ }
112
+ this._itemsPool || (this._itemsPool = []);
113
+ const p = this._itemsPool;
114
+ for (let l = o; l < h; l++) {
115
+ const u = this._getItemKey(l);
116
+ if (!u) continue;
117
+ const m = p[l - o];
118
+ m ? (m.key = u, m.index = l, m.top = this._getItemTop(u)) : p.push({ key: u, index: l, top: this._getItemTop(u) });
119
+ }
120
+ return h - o ? p.slice(0, h - o) : [];
121
+ }
122
+ }
123
+ function f(d) {
124
+ let t = null;
125
+ return function(...e) {
126
+ const n = this;
127
+ t && cancelAnimationFrame(t), t = requestAnimationFrame(() => d?.apply(n, e));
128
+ };
129
+ }
130
+ class T {
131
+ static containerStyle = { position: "relative", "overflow-anchor": "none" };
132
+ _options = {};
133
+ //配置项
134
+ _containerEl = null;
135
+ //滚动容器
136
+ _resizeObserver = null;
137
+ //监听元素变化
138
+ _listTop = 0;
139
+ //列表顶部
140
+ isInitialized = !1;
141
+ //是否初始化
142
+ _core = null;
143
+ _sliceData = [];
144
+ _preScrollTop = 0;
145
+ _scrollingUp = !1;
146
+ constructor(t = {}) {
147
+ const {
148
+ isWindowScroll: e = !1,
149
+ getContainerElement: n = null,
150
+ getKey: s = null,
151
+ itemSelector: r,
152
+ overscan: i = 10,
153
+ estimatedHeight: o,
154
+ onChange: h = null,
155
+ gap: a
156
+ } = t || {};
157
+ if (typeof n != "function" || typeof s != "function")
158
+ throw new Error("The parameters `getContainerElement` `getKey` must be a function");
159
+ if (!r) throw new Error("The parameters `itemSelector` cannot be null");
160
+ this._options = {
161
+ isWindowScroll: e,
162
+ getContainerElement: n,
163
+ itemSelector: r,
164
+ onChange: h
165
+ };
166
+ const c = new S({
167
+ overscan: i,
168
+ getKey: s,
169
+ estimatedHeight: o,
170
+ chunkSize: 100,
171
+ gap: a
172
+ }), _ = f((p) => {
173
+ const l = p.map((m) => {
174
+ const I = Number(m.target.dataset.index);
175
+ let g = m.borderBoxSize;
176
+ g = Array.isArray(g) ? g[0] : g;
177
+ const y = g.blockSize;
178
+ return { index: I, height: y };
179
+ });
180
+ let u = c.batchUpdateHeight(l);
181
+ Math.abs(u) > 0 && this._onHeightChange(u);
182
+ });
183
+ this._resizeObserver = new ResizeObserver(_), this._core = c, this.isInitialized = !1;
184
+ }
185
+ _isHTMLElement = (t) => t instanceof HTMLElement;
186
+ _getScrollDom = () => this._options.isWindowScroll ? window : this._containerEl;
187
+ _scrollTo = (t) => this._getScrollDom()?.scrollTo?.(0, t);
188
+ _createItemStyle = (t = 0) => ({
189
+ position: "absolute",
190
+ top: 0,
191
+ left: 0,
192
+ width: this._options.columnWidth || "100%",
193
+ transform: `translate3d(${this._options.columnLeft || 0}, ${t}px, 0)`
194
+ });
195
+ _getItemTop = (t) => this._core?.getItemTop(t);
196
+ _getScrollTop() {
197
+ let t = this._options.isWindowScroll ? window.scrollY : this._containerEl?.scrollTop;
198
+ return Math.max(0, t - this._listTop);
199
+ }
200
+ _onHeightChange(t) {
201
+ this._changeData(!1), this._scrollingUp && this._scrollTo(this._getScrollTop() + t), this._scrollingUp = !1;
202
+ }
203
+ _handleScroll = f(() => {
204
+ const t = this._getScrollTop();
205
+ this._preScrollTop !== t && (this._changeData(!0), this._scrollingUp = this._preScrollTop > t, this._preScrollTop = t);
206
+ });
207
+ _changeData(t = !0) {
208
+ if (t) {
209
+ const e = this._options.isWindowScroll ? window.innerHeight : this._containerEl?.clientHeight;
210
+ this._sliceData = this._core.getVirtualItems(e, this._getScrollTop());
211
+ }
212
+ this._sliceData.forEach((e) => {
213
+ e.top = this._getItemTop(e.index), e.style = this._createItemStyle(e.top);
214
+ }), this._options?.onChange?.({ hasNewData: t });
215
+ }
216
+ _getCurrentRenderedItem = () => {
217
+ const t = this._options.isWindowScroll ? document : this._containerEl;
218
+ return Array.from(t.querySelectorAll(this._options.itemSelector) || []);
219
+ };
220
+ _calculateListTop() {
221
+ this._options.isWindowScroll ? this._listTop = window.scrollY + (this._containerEl?.getBoundingClientRect?.()?.top || 0) : this._listTop = 0;
222
+ }
223
+ init = () => {
224
+ if (this.isInitialized) return;
225
+ const { getContainerElement: t } = this._options;
226
+ if (this._containerEl = t?.(), !this._isHTMLElement(this._containerEl)) throw new Error("getContainerElement must return HTMLElement");
227
+ this._getScrollDom()?.addEventListener("scroll", this._handleScroll), this._calculateListTop(), this.isInitialized = !0;
228
+ };
229
+ reset = () => {
230
+ this.isInitialized && (this._resizeObserver?.disconnect(), this.isInitialized = !1, this._listTop = 0, this._preScrollTop = 0, this._core?.reset(), this._getScrollDom()?.removeEventListener("scroll", this._handleScroll), this._scrollTo(0), this._changeData(!0));
231
+ };
232
+ setItemCount(t) {
233
+ if (!this.isInitialized) throw new Error("library not initialized");
234
+ const e = Number(t || 0);
235
+ this._core?.setItemCount(e), this._changeData(!0);
236
+ }
237
+ updateRenderedItemSize() {
238
+ this._resizeObserver?.disconnect(), this._getCurrentRenderedItem().forEach((t, e) => {
239
+ t.dataset.index = this._sliceData[e]?.index, this._resizeObserver?.observe(t);
240
+ });
241
+ }
242
+ scrollToIndex(t) {
243
+ requestAnimationFrame(() => {
244
+ const e = this._getItemTop(t);
245
+ this._scrollTo(this._options.isWindowScroll ? this._listTop + e : e);
246
+ });
247
+ }
248
+ getTotalHeight = () => Math.max(0, this._core?.getTotalHeight());
249
+ getRangeItems = () => this._sliceData || [];
250
+ setColumnOffsetStyle(t, e) {
251
+ typeof t != "number" || typeof e != "number" || (this._options.columnWidth = t + "px", this._options.columnLeft = e + "px", this._changeData(!1));
252
+ }
253
+ calculateContainerPageTop() {
254
+ this._calculateListTop(), this._changeData(!0);
255
+ }
256
+ }
257
+ export {
258
+ T as VirtualListAdapter,
259
+ S as VirtualListCore
260
+ };
@@ -0,0 +1 @@
1
+ (function(m,g){typeof exports=="object"&&typeof module<"u"?g(exports):typeof define=="function"&&define.amd?define(["exports"],g):(m=typeof globalThis<"u"?globalThis:m||self,g(m.xVirtualCore={}))})(this,(function(m){"use strict";class g{_options={};_keyToIndexObj={};_chunkList=[];_itemSizeMap=new Map;_itemCount=0;constructor(t={}){const{getKey:e=null,estimatedHeight:n=null,overscan:s=10,chunkSize:r=100,gap:i}=t||{};if(typeof e!="function")throw new Error("The parameter getKey must be a function");if(typeof n!="function")throw new Error("The parameter estimatedHeight must be a function");this._options={getKey:e,estimatedHeight:n,overscan:s,chunkSize:r,gap:Number(i)||0}}_getItemHeight=t=>this._itemSizeMap.get(t)||0;_getChunkIndex=t=>Math.floor(t/this._options.chunkSize);_getItemTop=t=>{const e=this._keyToIndexObj[t],n=this._chunkList[this._getChunkIndex(e)];let s=n?.top||0;return n&&(s+=n.prefixSums[Math.max(e-n.start,0)]||0),s+((e||0)+1)*this._options.gap};_setItemHeight=(t,e)=>this._itemSizeMap.set(t,e||0);_getItemKey=t=>this._options?.getKey?.(t)||null;_computeChunk(t){let e=0;const n=t.prefixSums;for(let s=t.start;s<t.end;s++)n[s-t.start]=e,e+=this._getItemHeight(this._getItemKey(s));t.height=e}_rebuildChunkListFrom(t=0){const e=this._itemCount;if(!e)return;const{chunkSize:n}=this._options,s=this._getChunkIndex(t);this._chunkList=this._chunkList?.slice?.(0,s)||[];const r=this._chunkList[this._chunkList.length-1];let i=(r?.top||0)+(r?.height||0);const o=Math.ceil(e/n)-s;for(let h=0;h<o;h++){const a=(s+h)*n,c=Math.min(a+n,e),_={start:a,end:c,top:i,height:0,prefixSums:new Float32Array(c-a)};this._computeChunk(_),this._chunkList.push(_),i+=_.height}}_updateChunkListFrom(t=new Set){const e=this._chunkList,n=Array.from(t||[]).sort((i,o)=>i-o);if(!n.length)return;const s=n[0];n?.forEach(i=>this._computeChunk(e[i]));let r=(e[s]?.top||0)+(e[s]?.height||0);for(let i=s+1;i<e.length;i++)e[i].top=r,r+=e[i].height}_binarySearch(t,e){if(e<=0||typeof t!="number"||isNaN(t))return 0;let n=0,s=e-1;for(;n<=s;){const r=Math.floor((n+s)/2),i=this._getItemKey(r);if(i==null||i===""){s=r-1;continue}const o=this._getItemTop(i);if(t>=o&&t<o+this._getItemHeight(i))return r;t<o?s=r-1:n=r+1}return Math.min(Math.max(0,s),e-1)}getItemTop=t=>this._getItemTop(this._getItemKey(t));getTotalHeight(){const t=this._getItemKey(this._itemCount-1);return this._getItemTop(t)+this._getItemHeight(t)+this._options.gap}reset(){this._itemSizeMap.clear(),this._keyToIndexObj={},this._chunkList=[],this._itemCount=0,this._itemsPool=[]}setItemCount(t){if(!t||t<0)return this.reset();let e=-1;const n=this._getItemKey(this._itemCount-1),r=t>this._itemCount&&this._itemCount-1==this._keyToIndexObj[n]?this._itemCount:0;for(let i=r;i<t;i++){const o=this._getItemKey(i);o&&(e<0&&this._keyToIndexObj[o]!==i&&(e=i),this._keyToIndexObj[o]=i,this._itemSizeMap.has(o)||this._setItemHeight(o,Number(this._options.estimatedHeight?.(i)||0)))}this._itemCount=t,e>-1&&this._rebuildChunkListFrom(e)}batchUpdateHeight(t=[]){let e=0;if(!Array.isArray(t))return e;const n=new Set;return t.forEach(s=>{const{index:r,height:i}=s||{};if(typeof r!="number"||typeof i!="number"||r<0)return;const o=this._getItemKey(r);!o||this._getItemHeight(o)===i||(e+=i-this._getItemHeight(o),this._setItemHeight(o,i),n.add(this._getChunkIndex(r)))}),n.size>0&&this._updateChunkListFrom(n),e}getVirtualItems(t,e){if(!t)return[];const{overscan:n}=this._options,s=this._itemCount,r=this._binarySearch(e,s),i=n||10;let o=Math.max(r-i,0),h=r,a=0;for(;h<s&&a<t;)a+=this._getItemHeight(this._getItemKey(h)),h++;h=Math.min(h+i,s);const c=i+i,_=h-o;if(c>_){let l=c-_;o==0?h=Math.min(h+l,s):h==s&&(o=Math.max(o-l,0))}this._itemsPool||(this._itemsPool=[]);const d=this._itemsPool;for(let l=o;l<h;l++){const u=this._getItemKey(l);if(!u)continue;const p=d[l-o];p?(p.key=u,p.index=l,p.top=this._getItemTop(u)):d.push({key:u,index:l,top:this._getItemTop(u)})}return h-o?d.slice(0,h-o):[]}}function y(I){let t=null;return function(...e){const n=this;t&&cancelAnimationFrame(t),t=requestAnimationFrame(()=>I?.apply(n,e))}}class S{static containerStyle={position:"relative","overflow-anchor":"none"};_options={};_containerEl=null;_resizeObserver=null;_listTop=0;isInitialized=!1;_core=null;_sliceData=[];_preScrollTop=0;_scrollingUp=!1;constructor(t={}){const{isWindowScroll:e=!1,getContainerElement:n=null,getKey:s=null,itemSelector:r,overscan:i=10,estimatedHeight:o,onChange:h=null,gap:a}=t||{};if(typeof n!="function"||typeof s!="function")throw new Error("The parameters `getContainerElement` `getKey` must be a function");if(!r)throw new Error("The parameters `itemSelector` cannot be null");this._options={isWindowScroll:e,getContainerElement:n,itemSelector:r,onChange:h};const c=new g({overscan:i,getKey:s,estimatedHeight:o,chunkSize:100,gap:a}),_=y(d=>{const l=d.map(p=>{const T=Number(p.target.dataset.index);let f=p.borderBoxSize;f=Array.isArray(f)?f[0]:f;const b=f.blockSize;return{index:T,height:b}});let u=c.batchUpdateHeight(l);Math.abs(u)>0&&this._onHeightChange(u)});this._resizeObserver=new ResizeObserver(_),this._core=c,this.isInitialized=!1}_isHTMLElement=t=>t instanceof HTMLElement;_getScrollDom=()=>this._options.isWindowScroll?window:this._containerEl;_scrollTo=t=>this._getScrollDom()?.scrollTo?.(0,t);_createItemStyle=(t=0)=>({position:"absolute",top:0,left:0,width:this._options.columnWidth||"100%",transform:`translate3d(${this._options.columnLeft||0}, ${t}px, 0)`});_getItemTop=t=>this._core?.getItemTop(t);_getScrollTop(){let t=this._options.isWindowScroll?window.scrollY:this._containerEl?.scrollTop;return Math.max(0,t-this._listTop)}_onHeightChange(t){this._changeData(!1),this._scrollingUp&&this._scrollTo(this._getScrollTop()+t),this._scrollingUp=!1}_handleScroll=y(()=>{const t=this._getScrollTop();this._preScrollTop!==t&&(this._changeData(!0),this._scrollingUp=this._preScrollTop>t,this._preScrollTop=t)});_changeData(t=!0){if(t){const e=this._options.isWindowScroll?window.innerHeight:this._containerEl?.clientHeight;this._sliceData=this._core.getVirtualItems(e,this._getScrollTop())}this._sliceData.forEach(e=>{e.top=this._getItemTop(e.index),e.style=this._createItemStyle(e.top)}),this._options?.onChange?.({hasNewData:t})}_getCurrentRenderedItem=()=>{const t=this._options.isWindowScroll?document:this._containerEl;return Array.from(t.querySelectorAll(this._options.itemSelector)||[])};_calculateListTop(){this._options.isWindowScroll?this._listTop=window.scrollY+(this._containerEl?.getBoundingClientRect?.()?.top||0):this._listTop=0}init=()=>{if(this.isInitialized)return;const{getContainerElement:t}=this._options;if(this._containerEl=t?.(),!this._isHTMLElement(this._containerEl))throw new Error("getContainerElement must return HTMLElement");this._getScrollDom()?.addEventListener("scroll",this._handleScroll),this._calculateListTop(),this.isInitialized=!0};reset=()=>{this.isInitialized&&(this._resizeObserver?.disconnect(),this.isInitialized=!1,this._listTop=0,this._preScrollTop=0,this._core?.reset(),this._getScrollDom()?.removeEventListener("scroll",this._handleScroll),this._scrollTo(0),this._changeData(!0))};setItemCount(t){if(!this.isInitialized)throw new Error("library not initialized");const e=Number(t||0);this._core?.setItemCount(e),this._changeData(!0)}updateRenderedItemSize(){this._resizeObserver?.disconnect(),this._getCurrentRenderedItem().forEach((t,e)=>{t.dataset.index=this._sliceData[e]?.index,this._resizeObserver?.observe(t)})}scrollToIndex(t){requestAnimationFrame(()=>{const e=this._getItemTop(t);this._scrollTo(this._options.isWindowScroll?this._listTop+e:e)})}getTotalHeight=()=>Math.max(0,this._core?.getTotalHeight());getRangeItems=()=>this._sliceData||[];setColumnOffsetStyle(t,e){typeof t!="number"||typeof e!="number"||(this._options.columnWidth=t+"px",this._options.columnLeft=e+"px",this._changeData(!1))}calculateContainerPageTop(){this._calculateListTop(),this._changeData(!0)}}m.VirtualListAdapter=S,m.VirtualListCore=g,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})}));
package/index.d.ts ADDED
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Type declarations for VirtualListCore (index.d.ts)
3
+ * Generated from the provided JavaScript implementation.
4
+ *
5
+ * Notes:
6
+ * - Keys in the original code can be strings or numbers; we type them as `string | number`.
7
+ * - _keyToIndexObj in the JS uses a plain object (which coerces numeric keys to strings).
8
+ * - If you prefer preserving numeric keys, consider using `Map<string|number, number>` in the TS implementation.
9
+ */
10
+
11
+ export interface VirtualListOptions {
12
+ /**
13
+ * Return the key for a given item index. May return null/undefined when the item key is not available.
14
+ */
15
+ getKey: (index: number) => string | number | null | undefined
16
+
17
+ /**
18
+ * Return the estimated height (in px) for a given item index.
19
+ */
20
+ estimatedHeight: (index: number) => number
21
+
22
+ /** Number of extra items to render before/after the visible window. Default: 10. */
23
+ overscan?: number
24
+
25
+ /** Size of each chunk used internally for prefix-sum grouping. Default: 100. */
26
+ chunkSize?: number
27
+
28
+ /** Gap in pixels between items. Default: 0. */
29
+ gap?: number
30
+ }
31
+
32
+ export interface Chunk {
33
+ start: number
34
+ end: number
35
+ top: number
36
+ height: number
37
+ prefixSums: Float32Array
38
+ }
39
+
40
+ export interface VirtualItem {
41
+ key: string | number
42
+ index: number
43
+ top: number
44
+ }
45
+
46
+ export interface BatchHeightItem {
47
+ index: number
48
+ height: number
49
+ }
50
+
51
+ /**
52
+ * VirtualListCore: core calculation engine for variable-height virtual lists.
53
+ *
54
+ * Only the public API is declared here. The original implementation contains
55
+ * many internal helpers and caches prefixed with `_` — these are declared
56
+ * as private fields for completeness, but you can remove them if you prefer
57
+ * a minimal declaration.
58
+ */
59
+ export declare class VirtualListCore {
60
+ constructor(options: VirtualListOptions)
61
+
62
+ /** Internal options passed in constructor */
63
+ private _options: VirtualListOptions
64
+
65
+ /** Map/object from item key -> item index. Note: JS implementation used plain object. */
66
+ private _keyToIndexObj: Record<string, number>
67
+
68
+ /** Chunk array used for prefix-sum calculations */
69
+ private _chunkList: Chunk[]
70
+
71
+ /** Cache of measured heights: key -> height (px) */
72
+ private _itemSizeMap: Map<string | number, number>
73
+
74
+ /** Total number of items currently tracked */
75
+ private _itemCount: number
76
+
77
+ /** Optional reusable item pool used by getVirtualItems */
78
+ private _itemsPool?: VirtualItem[]
79
+
80
+ /**
81
+ * Get the top offset (px) of the item at `index`.
82
+ * Returns 0 when index is invalid in the original implementation.
83
+ */
84
+ getItemTop(index: number): number
85
+
86
+ /** Get the total scrollable height (px) computed from known measurements. */
87
+ getTotalHeight(): number
88
+
89
+ /** Reset all caches and counters (clears measured heights, keys, chunks, etc.). */
90
+ reset(): void
91
+
92
+ /**
93
+ * Update internal item count. Accepts non-negative integer. When newItemCount
94
+ * is falsy or negative, reset() is invoked.
95
+ */
96
+ setItemCount(newItemCount: number): void
97
+
98
+ /**
99
+ * Batch update measured heights. Returns the total delta in height (new - old).
100
+ * Invalid entries are ignored.
101
+ */
102
+ batchUpdateHeight(arr?: BatchHeightItem[]): number
103
+
104
+ /**
105
+ * Main API to retrieve the currently visible (and overscanned) virtual items.
106
+ * - viewportHeight: visible viewport height in pixels
107
+ * - scrollTop: current scrollTop in pixels
108
+ * Returns an array of VirtualItem objects { key, index, top } in ascending index order.
109
+ */
110
+ getVirtualItems(viewportHeight: number, scrollTop: number): VirtualItem[]
111
+ }
112
+
113
+ export function debounceRAF<T extends (...args: any[]) => any>(func: T): (...args: Parameters<T>) => void
114
+
115
+ export interface VirtualListAdapterOptions {
116
+ isWindowScroll?: boolean
117
+ /** Should return the scrolling container HTMLElement (or null when unavailable) */
118
+ getContainerElement: () => HTMLElement | null
119
+ /** Return a stable key for the given index. May return null/undefined when not available. */
120
+ getKey: (index: number) => string | number | null | undefined
121
+ /** CSS selector for item elements that will be measured/observed (required) */
122
+ itemSelector: string
123
+ overscan?: number
124
+ estimatedHeight?: (index: number) => number
125
+ onChange?: (payload: { hasNewData: boolean }) => void
126
+ gap?: number
127
+ }
128
+
129
+ /** Minimal public surface of the adapter. Internal helpers prefixed with `_` are intentionally omitted from the public API here. */
130
+ export declare class VirtualListAdapter {
131
+ static containerStyle: Record<string, string>
132
+
133
+ constructor(options?: VirtualListAdapterOptions)
134
+
135
+ /** Whether adapter has been initialized via `init()` */
136
+ isInitialized: boolean
137
+
138
+ /** Initialize the adapter and attach scroll listeners. Throws if `getContainerElement` does not return an HTMLElement. */
139
+ init(): void
140
+
141
+ /** Reset internal state, disconnect observers and remove listeners. */
142
+ reset(): void
143
+
144
+ /** Provide the current number of items. Will call through to the core and refresh rendered slice. */
145
+ setItemCount(newValLength: number): void
146
+
147
+ /** Observe currently rendered DOM nodes to measure their sizes. */
148
+ updateRenderedItemSize(): void
149
+
150
+ /** Scroll to a specific item index (animated via RAF). */
151
+ scrollToIndex(index: number): void
152
+
153
+ /** Return total content height as computed by the core. */
154
+ getTotalHeight(): number
155
+
156
+ /** Return the currently rendered virtual items array. */
157
+ getRangeItems(): import('./index.d.ts').VirtualItem[]
158
+
159
+ /** Set column width/left offset used to produce item styles. */
160
+ setColumnOffsetStyle(width: number, left: number): void
161
+
162
+ /** Recalculate top offset (useful when container position changes) and refresh slice. */
163
+ calculateContainerPageTop(): void
164
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@x-virtual/core",
3
+ "private": false,
4
+ "version": "0.0.1",
5
+ "description": "A lightweight, cross-platform virtual layout library supporting dynamic-height lists and grid layouts with smooth scrolling and efficient rendering.",
6
+ "homepage": "https://github.com/LIFICN/x-virtual-layout",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/LIFICN/x-virtual-layout.git",
10
+ "directory": "packages/core"
11
+ },
12
+ "type": "module",
13
+ "files": [
14
+ "dist",
15
+ "index.d.ts"
16
+ ],
17
+ "main": "./dist/xVirtualCore.umd.cjs",
18
+ "module": "./dist/xVirtualCore.js",
19
+ "types": "./index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./index.d.ts",
23
+ "import": "./dist/xVirtualCore.js",
24
+ "require": "./dist/xVirtualCore.umd.cjs"
25
+ }
26
+ },
27
+ "devDependencies": {
28
+ "vite": "^7.3.1"
29
+ },
30
+ "scripts": {
31
+ "dev": "vite build --watch",
32
+ "build": "vite build"
33
+ }
34
+ }