Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@af-utils/virtual-core

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@af-utils/virtual-core - npm Package Compare versions

Comparing version 0.0.3 to 0.0.4

8

lib/bundlesize.index.js

@@ -1,4 +0,4 @@

export const raw = 7261;
export const min = 4655;
export const minGz = 2073;
export const minBrotli = 1899;
export const raw = 7540;
export const min = 4837;
export const minGz = 2144;
export const minBrotli = 1960;

@@ -1,4 +0,4 @@

export const raw = 7217;
export const min = 4702;
export const minGz = 2085;
export const minBrotli = 1913;
export const raw = 7496;
export const min = 4884;
export const minGz = 2151;
export const minBrotli = 1967;

@@ -17,4 +17,17 @@ /**

* @public
* Core framework-agnostic model.
* Stores item sizes, positions and provides fast way to calculate offsets
*
* Core framework-agnostic model.<br />
*
* @remarks
* What it does:<br />
* - stores item sizes and positions;<br />
* - tracks elements resizing;<br />
* - provides performant way to calculate offsets;<br />
* - deals with scrolling to item index or to offset;<br />
* - emits and allows to subscribe to {@link @af-utils/virtual-core#(VirtualScrollerEvent:variable) | events}.<br />
*
* What it doesn't do:<br />
* - rendering;<br />
* - styling;<br />
* - all other framework-related stuff.
*/

@@ -26,2 +39,5 @@ export declare class VirtualScroller {

private _scrollToKey;
private _desiredScrollIndex;
private _desiredScrollSmooth;
private _scrollSyncTimer;
private _alignedScrollPos;

@@ -32,3 +48,2 @@ private _scrollElementOffset;

private _availableWidgetSize;
private _scrollToTimer;
private _overscanCount;

@@ -76,3 +91,2 @@ private _estimatedItemSize;

* @remarks
*
* `window.resize` event must be used for window scroller, `ResizeObserver` must be used in other cases.

@@ -102,2 +116,5 @@ * `offsetWidth` is used as item size in horizontal mode, `offsetHeight` - in vertical.

* @returns item index;
*
* @remarks
* Time complexity: `O(log2(itemCount))`
*/

@@ -109,2 +126,5 @@ getIndex(offset: number): number;

* @returns pixel offset
*
* @remarks
* Time complexity: `O(log2(itemCount))`
*/

@@ -116,2 +136,5 @@ getOffset(index: number): number;

* @returns last cached item size
*
* @remarks
* Time complexity: `O(1)`
*/

@@ -134,20 +157,46 @@ getSize(itemIndex: number): number;

/**
* Tells model about scrollable element
* Informs model about scrollable element.
* @param element - scroller element
*
* @remarks
*
* Performs as destructor when null is passed
* will ne used as callback, so this function is bound
* Must be called with `null` before killing the instance.
*/
setScroller: (element: ScrollElement | null) => void;
setScroller(element: ScrollElement | null): void;
/**
* Should be used only when scrollable container has some "foreign" elements to properly integrate them.
* Informs model about items container element. Usually not needed.
*
* @param element - container element
*
* @remarks
* By default top/left offset between scroll container and first scrollable item is `0`.
* In this case just {@link VirtualScroller.setScroller} is needed.
* But extra element is needed when something "foreign" stands between scroll container and first scrollable item to measure distance between them.
* That extra element is represented as `ItemsContainer` on this schema:
*
* ```plaintext
* <ScrollContainer> |.|
* Some header |s|
* Another header |c|
* <ItemsContainer> |r|
* item 1 [o]
* item 2 [l]
* item 3 [l]
* ... [b]
* </ItemsContainer> |a|
* Some footer |r|
* </ScrollContainer> |.|
* ```
*
* Must be called with `null` before killing the instance.
*/
setContainer: (element: HTMLElement | null) => void;
setContainer(element: HTMLElement | null): void;
private _updateScrollerOffsetRaw;
private updateScrollerOffset;
/**
* Start observing size of `element` at `index`. Observing is finished if element is falsy.
* Start observing size of `element` at `index`. Observing is finished if element is `null`.
* @param index - item index
* @param element - element for item
*
* @remarks
* If an item was registered like `el( 5, HTMLElement )` it must be killed with `el( 5, null )` before killing the instance.
*/

@@ -157,9 +206,15 @@ el(index: number, element: HTMLElement | null): void;

/**
* Start observing size of sticky header `element`. Observing is finished if element is falsy.
* Start observing size of sticky header `element`. Observing is finished if element is `null`.
* @param element - header element
*
* @remarks
* Must be called with `null` before killing the instance.
*/
setStickyHeader(element: HTMLElement | null): void;
/**
* Start observing size of sticky footer `element`. Observing is finished if element is falsy.
* Start observing size of sticky footer `element`. Observing is finished if element is `null`.
* @param element - footer element
*
* @remarks
* Must be called with `null` before killing the instance.
*/

@@ -187,2 +242,4 @@ setStickyFooter(element: HTMLElement | null): void;

private _updateRangeFromStart;
private _killScrollEnd;
private _attemptToScrollToIndex;
/**

@@ -195,3 +252,2 @@ * Scroll to pixel offset

scrollToOffset(offset: number, smooth?: boolean): void;
private _attemptToScrollToIndex;
/**

@@ -219,9 +275,18 @@ * Scroll to item index

* Possible events, supported by {@link VirtualScroller.on} method
*
* @remarks
* Events Description: <br />
* - `RANGE`: {@link VirtualScroller.from} or {@link VirtualScroller.to} was changed; <br />
* - `SCROLL_SIZE`: sum of all item sizes was changed; <br />
* - `SIZES`: at least one item size was changed.
*
* @privateRemarks
* Did not export enum because I don't like reverse-mapped result in code.
* Did not refer to InternalEvent because in this case api-extractor wants it to be exported.
* Did not use enum modifier because api-extractor doesn't support it.
* Used `Events Desctipton` just because list without header is rendered badly by api-documenter.
*/
export declare const VirtualScrollerEvent: {
/** {@link VirtualScroller.from} or {@link VirtualScroller.to} was changed */
readonly RANGE: 0;
/** sum of all item sizes changed */
readonly SCROLL_SIZE: 1;
/** at least one item size changed */
readonly SIZES: 2;

@@ -238,20 +303,74 @@ };

* @public
* {@link VirtualScroller} constructor argument type
* All {@link VirtualScroller} parameters (that may / may not change over time).
*
* @remarks
* Implemented as interface for better documentation output (api-extractor)
*/
export declare type VirtualScrollerInitialParams = VirtualScrollerRuntimeParams & {
export declare interface VirtualScrollerInitialParams extends VirtualScrollerRuntimeParams {
/**
* Scroll container orientation.
*
* @remarks
* Determines properties used for dimension/scroll calculations, for example: <br />
* - `scrollTop` / `scrollLeft`;<br />
* - `height` / `width`;<br />
* - `innerHeight` / `innerWidth`.
*/
horizontal?: boolean;
/**
* Estimated size of scroll element.
*
* @remarks
* Actual size is always reported by `ResizeObserver`,
* but this property together with {@link VirtualScrollerRuntimeParams.estimatedItemSize} and {@link VirtualScrollerRuntimeParams.overscanCount} can be used in server-side rendering.
*
* Quantity of SSR-rendered elements can be calculated this way:
*
* ```typescript
* Math.min( itemCount, Math.ceil( estimatedWidgetSize / estimatedItemSize ) + overscanCount )
* ```
*/
estimatedWidgetSize?: number;
/**
* Estimated distance between top/left edges of scrollable container and first scrollable item.
*
* @remarks
* Does not equal `0` only when scrollable container and items container are different elements.
* {@link @af-utils/virtual-core#VirtualScroller.setContainer} has more explanation.
*/
estimatedScrollElementOffset?: number;
};
}
/**
* @public
* {@link VirtualScroller.set} argument type
* {@link VirtualScroller} parameters that may change over time.
* Used as {@link VirtualScroller.set} argument type.
*
* @remarks
* Implemented as interface for better documentation output (api-extractor)
*/
export declare type VirtualScrollerRuntimeParams = {
export declare interface VirtualScrollerRuntimeParams {
/**
* Amount of items rendered before or after visible ones.
* *
* @remarks
* Render place depends on scroll direction:<br />
* - if scrolling is done forward - these items are rendered after visible ones;<br />
* - If backward - before.
*/
overscanCount?: number;
/**
* Total items quantity
*/
itemCount?: number;
/**
* Estimated height/width of scrollable item. Orientation is determined by {@link VirtualScrollerInitialParams.horizontal}.
* *
* @remarks
* Actual size is always reported by internal `ResizeObserver`.
* Bad item size assumptions can turn into shaky scrolling experience. Accuracy here is rewarded.
*/
estimatedItemSize?: number;
};
}
export { }

@@ -1,6 +0,8 @@

const t = 2147483647, s = {
import "@af-utils/scrollend-polyfill";
const t = {
RANGE: 0,
SCROLL_SIZE: 1,
SIZES: 2
}, i = [ 0, 1, 2 ], e = Uint32Array, h = "test" === process.env.NODE_ENV ? class {
}, s = [ 0, 1, 2 ], i = Uint32Array, e = "test" === process.env.NODE_ENV ? class {
constructor(t) {}

@@ -10,24 +12,24 @@ observe() {}

disconnect() {}
} : ResizeObserver, o = t => t(), r = (t, s) => {
} : ResizeObserver, h = t => t(), o = (t, s) => {
if (!t) throw Error(s);
}, n = (t, s) => {
}, r = (t, s) => {
if (t instanceof Element) {
const i = new h(s);
const i = new e(s);
return i.observe(t), () => i.disconnect();
}
return s(), addEventListener("resize", s), () => removeEventListener("resize", s);
}, l = (t, s, i) => {
const h = new e(s);
return h.set(t), h.fill(i, t.length), h;
}, c = t => {
const s = t.length + 1, i = new e(s);
i.set(t, 1);
for (let t, e = 1; s > e; e++) t = e + (e & -e), s > t && (i[t] += i[e]);
return i;
}, a = (t, s, i, e) => {
}, n = (t, s, e) => {
const h = new i(s);
return h.set(t), h.fill(e, t.length), h;
}, l = t => {
const s = t.length + 1, e = new i(s);
e.set(t, 1);
for (let t, i = 1; s > i; i++) t = i + (i & -i), s > t && (e[t] += e[i]);
return e;
}, c = (t, s, i, e) => {
for (;e > s; s += s & -s) t[s] += i;
}, u = (t, s, i) => {
}, a = (t, s, i) => {
for (;i > s; s += s & -s) ;
return Math.min(s, t.length);
}, _ = (t, s) => t.getBoundingClientRect()[s], f = (t, s, i, e) => s && t && t !== s ? t[i] + Math.round(_(s, e) - (t instanceof HTMLElement ? _(t, e) : 0)) : 0, m = new Set, d = {
}, u = (t, s) => t.getBoundingClientRect()[s], _ = (t, s, i, e) => s && t && t !== s ? t[i] + Math.round(u(s, e) - (t instanceof HTMLElement ? u(t, e) : 0)) : 0, f = new Set, d = {
t: 0,

@@ -38,18 +40,18 @@ i() {

h() {
0 == --this.t && (m.forEach(o), m.clear());
0 == --this.t && (f.forEach(h), f.clear());
},
o: t => m.add(t)
o: t => f.add(t)
}, m = {
box: "border-box"
}, S = {
box: "border-box"
}, v = {
passive: !0
}, z = new e(0), E = [ "offsetHeight", "offsetWidth", "innerHeight", "innerWidth" ], p = [ "scrollTop", "scrollLeft", "scrollY", "scrollX" ], b = [ "blockSize", "inlineSize" ], T = [ "top", "left" ], y = (t, s) => Math.round(t[0][s]);
}, E = 2147483647, v = new i(0), z = [ "offsetHeight", "offsetWidth", "innerHeight", "innerWidth" ], p = [ "scrollTop", "scrollLeft", "scrollY", "scrollX" ], b = [ "blockSize", "inlineSize" ], y = [ "top", "left" ], x = (t, s) => Math.round(t[0][s]);
class x {
l=E[0];
class T {
l=z[0];
u=p[0];
_=b[0];
m=T[0];
S=0;
v=0;
m=y[0];
S=-1;
v=!1;
p=0;

@@ -59,9 +61,11 @@ T=0;

O=0;
I=0;
k=0;
C=3;
I=40;
R=null;
k=null;
L=z;
F=z;
H=0;
R=40;
L=null;
F=null;
H=v;
K=v;
P=0;
horizontal=!1;

@@ -72,153 +76,160 @@ scrollSize=0;

sizesHash=0;
K=new Map;
P=new Map;
W=[ null, null ];
A=[ 0, 0 ];
G=new h((t => {
W=new Map;
A=new Map;
G=[ null, null ];
Z=[ 0, 0 ];
q=new e((t => {
let s = 0;
for (const i of t) {
const t = this.W.indexOf(i.target);
const t = this.G.indexOf(i.target);
if (-1 !== t) {
const e = y(i.borderBoxSize, this._) - this.A[t];
this.A[t] += e, s += e;
const e = x(i.borderBoxSize, this._) - this.Z[t];
this.Z[t] += e, s += e;
}
}
this.Z(s);
this.N(s);
}));
q=new h((t => {
U=new e((t => {
let s = 0, i = !1;
const e = u(this.F, this.from + 1, this.to);
const e = a(this.K, this.from + 1, this.to);
for (const h of t) {
const t = this.K.get(h.target);
const t = this.W.get(h.target);
if (e > t) {
const o = y(h.borderBoxSize, this._) - this.L[t];
o && (i = !0, this.L[t] += o, s += o, a(this.F, t + 1, o, e));
const o = x(h.borderBoxSize, this._) - this.H[t];
o && (i = !0, this.H[t] += o, s += o, c(this.K, t + 1, o, e));
}
}
i && (d.i(), 0 !== s && (a(this.F, e, s, this.F.length), this.scrollSize += s, this.N(1),
0 > s && this.U()), this.sizesHash = this.sizesHash + 1 & 2147483647, this.N(2),
i && (d.i(), 0 !== s && (c(this.K, e, s, this.K.length), this.scrollSize += s, this.X(1),
0 > s && this.Y()), this.sizesHash = this.sizesHash + 1 & 2147483647, this.X(2),
d.h());
}));
X=i.map((() => []));
Y() {
const t = this.horizontal ? 1 : 0, s = t + 2 * (this.R instanceof Element ? 0 : 1);
this.l = E[s], this.u = p[s], this._ = b[t], this.m = T[t];
$=s.map((() => []));
j() {
const t = this.horizontal ? 1 : 0, s = t + 2 * (this.L instanceof Element ? 0 : 1);
this.l = z[s], this.u = p[s], this._ = b[t], this.m = y[t];
}
$=() => {
const t = (s = this.R, i = this.l, e = this.p, s[i] - e);
B=() => {
const t = (s = this.L, i = this.l, e = this.O, s[i] - e);
var s, i, e;
t !== this.M && (this.M = t, this.U());
t !== this.k && (this.k = t, this.Y());
};
Z(t) {
t && (d.i(), this.p += t, this.M -= t, this.N(1), this.U(), d.h());
N(t) {
t && (d.i(), this.O += t, this.k -= t, this.X(1), this.Y(), d.h());
}
j=() => {};
D=() => {};
constructor(t) {
t && (this.horizontal = !!t.horizontal, this.v = t.estimatedScrollElementOffset || 0,
this.M = t.estimatedWidgetSize ?? 200, this.set(t));
t && (this.horizontal = !!t.horizontal, this.M = t.estimatedScrollElementOffset || 0,
this.k = t.estimatedWidgetSize ?? 200, this.set(t));
}
on(t, s) {
return s.forEach((s => this.X[s].push(t))), () => s.forEach((s => this.X[s].splice(this.X[s].indexOf(t), 1)));
return s.forEach((s => this.$[s].push(t))), () => s.forEach((s => this.$[s].splice(this.$[s].indexOf(t), 1)));
}
N(t) {
this.X[t].forEach(0 === d.t ? o : d.o);
X(t) {
this.$[t].forEach(0 === d.t ? h : d.o);
}
getIndex(t) {
if (0 >= t) return 0;
if (t >= this.scrollSize) return this.T - 1;
if (t >= this.scrollSize) return this.I - 1;
let s = 0;
for (let i = this.H, e = 0; i > 0; i >>= 1) e = s + i, e <= this.T && t > this.F[e] && (s = e,
t -= this.F[e]);
for (let i = this.P, e = 0; i > 0; i >>= 1) e = s + i, e <= this.I && t > this.K[e] && (s = e,
t -= this.K[e]);
return s;
}
getOffset(t) {
"production" !== process.env.NODE_ENV && r(t <= this.T, "index must not be > itemCount");
"production" !== process.env.NODE_ENV && o(t <= this.I, "index must not be > itemCount");
let s = 0;
for (;t > 0; t -= t & -t) s += this.F[t];
for (;t > 0; t -= t & -t) s += this.K[t];
return s;
}
getSize(t) {
return "production" !== process.env.NODE_ENV && r(t < this.L.length, "itemIndex must be < itemCount in getSize"),
this.L[t];
return "production" !== process.env.NODE_ENV && o(t < this.H.length, "itemIndex must be < itemCount in getSize"),
this.H[t];
}
get visibleFrom() {
const t = this.B;
return t + (this.S - this.getOffset(t)) / this.L[t];
const t = this.J;
return t + (this.T - this.getOffset(t)) / this.H[t];
}
D=() => {
const t = this.S, s = Math.round(this.R[this.u]) - this.v;
s !== t && (this.S = s, s > t ? this.U() : this.J());
V=() => {
const t = this.T, s = Math.round(this.L[this.u]) - this.M;
s !== t && (this.T = s, s > t ? this.Y() : this.tt());
};
setScroller=t => {
t !== this.R && (this.j(), this.R?.removeEventListener("scroll", this.D), this.R = t,
t ? (this.Y(), this.j = n(t, this.$), t.addEventListener("scroll", this.D, v), this.V(),
this.D()) : (this.q.disconnect(), this.G.disconnect(), clearTimeout(this.O)));
};
setContainer=t => {
t !== this.k && (this.k = t, this.updateScrollerOffset());
};
V() {
const t = f(this.R, this.k, this.u, this.m) - this.v;
return this.v += t, this.S -= t, t;
setScroller(t) {
t !== this.L && (clearTimeout(this.p), this.D(), this.L?.removeEventListener("scroll", this.V),
this.st(), this.L = t, t && (this.j(), this.D = r(t, this.B), t.addEventListener("scroll", this.V, S),
this.p = setTimeout((() => {
this.it();
const t = this.S;
this.S = -1, this.V(), -1 !== t && this.scrollToIndex(t, !1);
}), 0)));
}
setContainer(t) {
t !== this.F && (this.F = t, this.updateScrollerOffset());
}
it() {
const t = _(this.L, this.F, this.u, this.m) - this.M;
return this.M += t, this.T -= t, t;
}
updateScrollerOffset() {
this.V() && this.R && this.D();
this.it() && this.L && this.V();
}
el(t, s) {
const i = this.P.get(t);
i && (this.P.delete(t), this.K.delete(i), this.q.unobserve(i)), s && (this.K.set(s, t),
this.P.set(t, s), this.q.observe(s, S));
const i = this.A.get(t);
i && (this.A.delete(t), this.W.delete(i), this.U.unobserve(i)), s && (this.W.set(s, t),
this.A.set(t, s), this.U.observe(s, m));
}
tt(t, s) {
const i = this.W[t];
i && (this.G.unobserve(i), this.Z(-this.A[t]), this.W[t] = null, this.A[t] = 0),
s && (this.W[t] = s, this.G.observe(s, S));
et(t, s) {
const i = this.G[t];
i && (this.q.unobserve(i), this.N(-this.Z[t]), this.G[t] = null, this.Z[t] = 0),
s && (this.G[t] = s, this.q.observe(s, m));
}
setStickyHeader(t) {
this.tt(0, t);
this.et(0, t);
}
setStickyFooter(t) {
this.tt(1, t);
this.et(1, t);
}
get B() {
return this.getIndex(this.S);
get J() {
return this.getIndex(this.T);
}
get st() {
return this.T && 1 + this.getIndex(this.S + this.M);
get ht() {
return this.I && 1 + this.getIndex(this.T + this.k);
}
U() {
const {st: t} = this;
t > this.to && (this.to = Math.min(this.T, t + this.C), this.from = this.B, this.N(0));
Y() {
const {ht: t} = this;
t > this.to && (this.to = Math.min(this.I, t + this.C), this.from = this.J, this.X(0));
}
J() {
const {B: t} = this;
t < this.from && (this.from = Math.max(0, t - this.C), this.to = this.st, this.N(0));
tt() {
const {J: t} = this;
t < this.from && (this.from = Math.max(0, t - this.C), this.to = this.ht, this.X(0));
}
st() {
this.L?.removeEventListener("scrollend", this.ot);
}
ot=() => {
const t = this.S, s = 0 | t, i = Math.min(this.scrollSize - this.k, this.getOffset(s) + Math.round(this.H[s] * (t - s)));
i === this.T ? (this.S = -1, this.st()) : this.scrollToOffset(i, this.v);
};
scrollToOffset(t, s) {
this.R?.scroll({
[this.m]: this.v + t,
this.L?.scroll({
[this.m]: this.M + t,
behavior: s ? "smooth" : void 0
});
}
it(t, s, i) {
clearTimeout(this.O);
const e = 0 | s, h = Math.min(this.scrollSize - this.M, this.getOffset(e) + Math.round(this.L[e] * (s - e)));
h !== this.S && this.R && --t && (this.scrollToOffset(h, i), this.O = setTimeout((() => this.it(t, s, i)), i ? 512 : 32));
}
scrollToIndex(t, s) {
this.it(16, t, s);
-1 === this.S && this.L?.addEventListener("scrollend", this.ot, S), this.S = t,
this.v = !!s, this.ot();
}
setItemCount(s) {
this.T !== s && (d.i(), r(t >= s, `itemCount must be <= 2147483647. Got: ${s}.`),
this.T = s, this.H = s && 1 << 31 - Math.clz32(s), s > this.L.length && (this.L = l(this.L, Math.min(s + 32, t), this.I || 40),
this.F = c(this.L)), this.scrollSize = this.getOffset(s), this.N(1), this.to > s && (this.to = -1),
this.U(), d.h());
setItemCount(t) {
this.I !== t && (d.i(), o(E >= t, `itemCount must be <= 2147483647. Got: ${t}.`),
this.I = t, this.P = t && 1 << 31 - Math.clz32(t), t > this.H.length && (this.H = n(this.H, Math.min(t + 32, E), this.R || 40),
this.K = l(this.H)), this.scrollSize = this.getOffset(t), this.X(1), this.to > t && (this.to = -1),
this.Y(), d.h());
}
set(t) {
let {overscanCount: s, itemCount: i, estimatedItemSize: e} = t;
e && (this.I = e), void 0 !== s && (this.C = s), void 0 !== i && this.setItemCount(i);
e && (this.R = e), void 0 !== s && (this.C = s), void 0 !== i && this.setItemCount(i);
}
}
export { x as VirtualScroller, s as VirtualScrollerEvent, i as _ALL_EVENTS };
export { T as VirtualScroller, t as VirtualScrollerEvent, s as _ALL_EVENTS };
//# sourceMappingURL=index.js.map

@@ -1,6 +0,8 @@

const t = 2147483647, s = {
import "@af-utils/scrollend-polyfill";
const t = {
RANGE: 0,
SCROLL_SIZE: 1,
SIZES: 2
}, i = [ 0, 1, 2 ], e = Uint32Array, h = class {
}, s = [ 0, 1, 2 ], i = Uint32Array, e = class {
constructor(t) {}

@@ -10,24 +12,24 @@ observe() {}

disconnect() {}
}, o = t => t(), r = (t, s) => {
}, h = t => t(), o = (t, s) => {
if (!t) throw Error(s);
}, n = (t, s) => {
}, r = (t, s) => {
if (t instanceof Element) {
const i = new h(s);
const i = new e(s);
return i.observe(t), () => i.disconnect();
}
return s(), addEventListener("resize", s), () => removeEventListener("resize", s);
}, l = (t, s, i) => {
const h = new e(s);
return h.set(t), h.fill(i, t.length), h;
}, c = t => {
const s = t.length + 1, i = new e(s);
i.set(t, 1);
for (let t, e = 1; s > e; e++) t = e + (e & -e), s > t && (i[t] += i[e]);
return i;
}, a = (t, s, i, e) => {
}, n = (t, s, e) => {
const h = new i(s);
return h.set(t), h.fill(e, t.length), h;
}, l = t => {
const s = t.length + 1, e = new i(s);
e.set(t, 1);
for (let t, i = 1; s > i; i++) t = i + (i & -i), s > t && (e[t] += e[i]);
return e;
}, c = (t, s, i, e) => {
for (;e > s; s += s & -s) t[s] += i;
}, u = (t, s, i) => {
}, a = (t, s, i) => {
for (;i > s; s += s & -s) ;
return Math.min(s, t.length);
}, _ = (t, s) => t.getBoundingClientRect()[s], f = (t, s, i, e) => s && t && t !== s ? t[i] + Math.round(_(s, e) - (t instanceof HTMLElement ? _(t, e) : 0)) : 0, m = new Set, d = {
}, u = (t, s) => t.getBoundingClientRect()[s], _ = (t, s, i, e) => s && t && t !== s ? t[i] + Math.round(u(s, e) - (t instanceof HTMLElement ? u(t, e) : 0)) : 0, f = new Set, d = {
t: 0,

@@ -38,29 +40,31 @@ i() {

h() {
0 == --this.t && (m.forEach(o), m.clear());
0 == --this.t && (f.forEach(h), f.clear());
},
o: t => m.add(t)
o: t => f.add(t)
}, m = {
box: "border-box"
}, S = {
box: "border-box"
}, E = {
passive: !0
}, v = new e(0), z = [ "offsetHeight", "offsetWidth", "innerHeight", "innerWidth" ], p = [ "scrollTop", "scrollLeft", "scrollY", "scrollX" ], b = [ "blockSize", "inlineSize" ], T = [ "top", "left" ], y = (t, s) => Math.round(t[0][s]);
}, E = 2147483647, v = new i(0), z = [ "offsetHeight", "offsetWidth", "innerHeight", "innerWidth" ], p = [ "scrollTop", "scrollLeft", "scrollY", "scrollX" ], y = [ "blockSize", "inlineSize" ], b = [ "top", "left" ], x = (t, s) => Math.round(t[0][s]);
class x {
class T {
l=z[0];
u=p[0];
_=b[0];
m=T[0];
S=0;
v=0;
_=y[0];
m=b[0];
S=-1;
v=!1;
p=0;
T=0;
M=0;
I=0;
O=0;
k=0;
C=3;
I=40;
k=null;
R=null;
L=v;
F=v;
H=0;
R=40;
L=null;
F=null;
H=v;
K=v;
P=0;
horizontal=!1;

@@ -71,153 +75,160 @@ scrollSize=0;

sizesHash=0;
K=new Map;
P=new Map;
W=[ null, null ];
A=[ 0, 0 ];
G=new h((t => {
W=new Map;
A=new Map;
G=[ null, null ];
Z=[ 0, 0 ];
q=new e((t => {
let s = 0;
for (const i of t) {
const t = this.W.indexOf(i.target);
const t = this.G.indexOf(i.target);
if (-1 !== t) {
const e = y(i.borderBoxSize, this._) - this.A[t];
this.A[t] += e, s += e;
const e = x(i.borderBoxSize, this._) - this.Z[t];
this.Z[t] += e, s += e;
}
}
this.Z(s);
this.N(s);
}));
q=new h((t => {
U=new e((t => {
let s = 0, i = !1;
const e = u(this.F, this.from + 1, this.to);
const e = a(this.K, this.from + 1, this.to);
for (const h of t) {
const t = this.K.get(h.target);
const t = this.W.get(h.target);
if (e > t) {
const o = y(h.borderBoxSize, this._) - this.L[t];
o && (i = !0, this.L[t] += o, s += o, a(this.F, t + 1, o, e));
const o = x(h.borderBoxSize, this._) - this.H[t];
o && (i = !0, this.H[t] += o, s += o, c(this.K, t + 1, o, e));
}
}
i && (d.i(), 0 !== s && (a(this.F, e, s, this.F.length), this.scrollSize += s, this.N(1),
0 > s && this.U()), this.sizesHash = this.sizesHash + 1 & 2147483647, this.N(2),
i && (d.i(), 0 !== s && (c(this.K, e, s, this.K.length), this.scrollSize += s, this.X(1),
0 > s && this.Y()), this.sizesHash = this.sizesHash + 1 & 2147483647, this.X(2),
d.h());
}));
X=i.map((() => []));
Y() {
const t = this.horizontal ? 1 : 0, s = t + 2 * (this.k instanceof Element ? 0 : 1);
this.l = z[s], this.u = p[s], this._ = b[t], this.m = T[t];
$=s.map((() => []));
j() {
const t = this.horizontal ? 1 : 0, s = t + 2 * (this.L instanceof Element ? 0 : 1);
this.l = z[s], this.u = p[s], this._ = y[t], this.m = b[t];
}
$=() => {
const t = (s = this.k, i = this.l, e = this.p, s[i] - e);
B=() => {
const t = (s = this.L, i = this.l, e = this.I, s[i] - e);
var s, i, e;
t !== this.M && (this.M = t, this.U());
t !== this.k && (this.k = t, this.Y());
};
Z(t) {
t && (d.i(), this.p += t, this.M -= t, this.N(1), this.U(), d.h());
N(t) {
t && (d.i(), this.I += t, this.k -= t, this.X(1), this.Y(), d.h());
}
j=() => {};
D=() => {};
constructor(t) {
t && (this.horizontal = !!t.horizontal, this.v = t.estimatedScrollElementOffset || 0,
this.M = t.estimatedWidgetSize ?? 200, this.set(t));
t && (this.horizontal = !!t.horizontal, this.M = t.estimatedScrollElementOffset || 0,
this.k = t.estimatedWidgetSize ?? 200, this.set(t));
}
on(t, s) {
return s.forEach((s => this.X[s].push(t))), () => s.forEach((s => this.X[s].splice(this.X[s].indexOf(t), 1)));
return s.forEach((s => this.$[s].push(t))), () => s.forEach((s => this.$[s].splice(this.$[s].indexOf(t), 1)));
}
N(t) {
this.X[t].forEach(0 === d.t ? o : d.o);
X(t) {
this.$[t].forEach(0 === d.t ? h : d.o);
}
getIndex(t) {
if (0 >= t) return 0;
if (t >= this.scrollSize) return this.T - 1;
if (t >= this.scrollSize) return this.O - 1;
let s = 0;
for (let i = this.H, e = 0; i > 0; i >>= 1) e = s + i, e <= this.T && t > this.F[e] && (s = e,
t -= this.F[e]);
for (let i = this.P, e = 0; i > 0; i >>= 1) e = s + i, e <= this.O && t > this.K[e] && (s = e,
t -= this.K[e]);
return s;
}
getOffset(t) {
"production" !== process.env.NODE_ENV && r(t <= this.T, "index must not be > itemCount");
"production" !== process.env.NODE_ENV && o(t <= this.O, "index must not be > itemCount");
let s = 0;
for (;t > 0; t -= t & -t) s += this.F[t];
for (;t > 0; t -= t & -t) s += this.K[t];
return s;
}
getSize(t) {
return "production" !== process.env.NODE_ENV && r(t < this.L.length, "itemIndex must be < itemCount in getSize"),
this.L[t];
return "production" !== process.env.NODE_ENV && o(t < this.H.length, "itemIndex must be < itemCount in getSize"),
this.H[t];
}
get visibleFrom() {
const t = this.B;
return t + (this.S - this.getOffset(t)) / this.L[t];
const t = this.J;
return t + (this.T - this.getOffset(t)) / this.H[t];
}
D=() => {
const t = this.S, s = Math.round(this.k[this.u]) - this.v;
s !== t && (this.S = s, s > t ? this.U() : this.J());
V=() => {
const t = this.T, s = Math.round(this.L[this.u]) - this.M;
s !== t && (this.T = s, s > t ? this.Y() : this.tt());
};
setScroller=t => {
t !== this.k && (this.j(), this.k?.removeEventListener("scroll", this.D), this.k = t,
t ? (this.Y(), this.j = n(t, this.$), t.addEventListener("scroll", this.D, E), this.V(),
this.D()) : (this.q.disconnect(), this.G.disconnect(), clearTimeout(this.O)));
};
setContainer=t => {
t !== this.R && (this.R = t, this.updateScrollerOffset());
};
V() {
const t = f(this.k, this.R, this.u, this.m) - this.v;
return this.v += t, this.S -= t, t;
setScroller(t) {
t !== this.L && (clearTimeout(this.p), this.D(), this.L?.removeEventListener("scroll", this.V),
this.st(), this.L = t, t && (this.j(), this.D = r(t, this.B), t.addEventListener("scroll", this.V, S),
this.p = setTimeout((() => {
this.it();
const t = this.S;
this.S = -1, this.V(), -1 !== t && this.scrollToIndex(t, !1);
}), 0)));
}
setContainer(t) {
t !== this.F && (this.F = t, this.updateScrollerOffset());
}
it() {
const t = _(this.L, this.F, this.u, this.m) - this.M;
return this.M += t, this.T -= t, t;
}
updateScrollerOffset() {
this.V() && this.k && this.D();
this.it() && this.L && this.V();
}
el(t, s) {
const i = this.P.get(t);
i && (this.P.delete(t), this.K.delete(i), this.q.unobserve(i)), s && (this.K.set(s, t),
this.P.set(t, s), this.q.observe(s, S));
const i = this.A.get(t);
i && (this.A.delete(t), this.W.delete(i), this.U.unobserve(i)), s && (this.W.set(s, t),
this.A.set(t, s), this.U.observe(s, m));
}
tt(t, s) {
const i = this.W[t];
i && (this.G.unobserve(i), this.Z(-this.A[t]), this.W[t] = null, this.A[t] = 0),
s && (this.W[t] = s, this.G.observe(s, S));
et(t, s) {
const i = this.G[t];
i && (this.q.unobserve(i), this.N(-this.Z[t]), this.G[t] = null, this.Z[t] = 0),
s && (this.G[t] = s, this.q.observe(s, m));
}
setStickyHeader(t) {
this.tt(0, t);
this.et(0, t);
}
setStickyFooter(t) {
this.tt(1, t);
this.et(1, t);
}
get B() {
return this.getIndex(this.S);
get J() {
return this.getIndex(this.T);
}
get st() {
return this.T && 1 + this.getIndex(this.S + this.M);
get ht() {
return this.O && 1 + this.getIndex(this.T + this.k);
}
U() {
const {st: t} = this;
t > this.to && (this.to = Math.min(this.T, t + this.C), this.from = this.B, this.N(0));
Y() {
const {ht: t} = this;
t > this.to && (this.to = Math.min(this.O, t + this.C), this.from = this.J, this.X(0));
}
J() {
const {B: t} = this;
t < this.from && (this.from = Math.max(0, t - this.C), this.to = this.st, this.N(0));
tt() {
const {J: t} = this;
t < this.from && (this.from = Math.max(0, t - this.C), this.to = this.ht, this.X(0));
}
st() {
this.L?.removeEventListener("scrollend", this.ot);
}
ot=() => {
const t = this.S, s = 0 | t, i = Math.min(this.scrollSize - this.k, this.getOffset(s) + Math.round(this.H[s] * (t - s)));
i === this.T ? (this.S = -1, this.st()) : this.scrollToOffset(i, this.v);
};
scrollToOffset(t, s) {
this.k?.scroll({
[this.m]: this.v + t,
this.L?.scroll({
[this.m]: this.M + t,
behavior: s ? "smooth" : void 0
});
}
it(t, s, i) {
clearTimeout(this.O);
const e = 0 | s, h = Math.min(this.scrollSize - this.M, this.getOffset(e) + Math.round(this.L[e] * (s - e)));
h !== this.S && this.k && --t && (this.scrollToOffset(h, i), this.O = setTimeout((() => this.it(t, s, i)), i ? 512 : 32));
}
scrollToIndex(t, s) {
this.it(16, t, s);
-1 === this.S && this.L?.addEventListener("scrollend", this.ot, S), this.S = t,
this.v = !!s, this.ot();
}
setItemCount(s) {
this.T !== s && (d.i(), r(t >= s, `itemCount must be <= 2147483647. Got: ${s}.`),
this.T = s, this.H = s && 1 << 31 - Math.clz32(s), s > this.L.length && (this.L = l(this.L, Math.min(s + 32, t), this.I || 40),
this.F = c(this.L)), this.scrollSize = this.getOffset(s), this.N(1), this.to > s && (this.to = -1),
this.U(), d.h());
setItemCount(t) {
this.O !== t && (d.i(), o(E >= t, `itemCount must be <= 2147483647. Got: ${t}.`),
this.O = t, this.P = t && 1 << 31 - Math.clz32(t), t > this.H.length && (this.H = n(this.H, Math.min(t + 32, E), this.R || 40),
this.K = l(this.H)), this.scrollSize = this.getOffset(t), this.X(1), this.to > t && (this.to = -1),
this.Y(), d.h());
}
set(t) {
let {overscanCount: s, itemCount: i, estimatedItemSize: e} = t;
e && (this.I = e), void 0 !== s && (this.C = s), void 0 !== i && this.setItemCount(i);
e && (this.R = e), void 0 !== s && (this.C = s), void 0 !== i && this.setItemCount(i);
}
}
export { x as VirtualScroller, s as VirtualScrollerEvent, i as _ALL_EVENTS };
export { T as VirtualScroller, t as VirtualScrollerEvent, s as _ALL_EVENTS };
//# sourceMappingURL=index.server.js.map

@@ -1,6 +0,1 @@

export declare const DEFAULT_OVERSCAN_COUNT = 3;
export declare const DEFAULT_ESTIMATED_WIDGET_SIZE = 200;
export declare const DEFAULT_ESTIMATED_ITEM_SIZE = 40;
export declare const SIZES_HASH_MODULO = 2147483647;
export declare const MAX_ITEM_COUNT = 2147483647;
/** @internal */

@@ -15,9 +10,18 @@ export declare const enum InternalEvent {

* Possible events, supported by {@link VirtualScroller.on} method
*
* @remarks
* Events Description: <br />
* - `RANGE`: {@link VirtualScroller.from} or {@link VirtualScroller.to} was changed; <br />
* - `SCROLL_SIZE`: sum of all item sizes was changed; <br />
* - `SIZES`: at least one item size was changed.
*
* @privateRemarks
* Did not export enum because I don't like reverse-mapped result in code.
* Did not refer to InternalEvent because in this case api-extractor wants it to be exported.
* Did not use enum modifier because api-extractor doesn't support it.
* Used `Events Desctipton` just because list without header is rendered badly by api-documenter.
*/
export declare const VirtualScrollerEvent: {
/** {@link VirtualScroller.from} or {@link VirtualScroller.to} was changed */
readonly RANGE: 0;
/** sum of all item sizes changed */
readonly SCROLL_SIZE: 1;
/** at least one item size changed */
readonly SIZES: 2;

@@ -24,0 +28,0 @@ };

@@ -5,4 +5,5 @@ /**

*/
import "@af-utils/scrollend-polyfill";
export { default as VirtualScroller } from "./models/VirtualScroller";
export { VirtualScrollerEvent, _ALL_EVENTS } from "./constants";
export * from "./types";
import { VirtualScrollerEvent } from "../../constants";
import type { ScrollElement, VirtualScrollerInitialParams, VirtualScrollerRuntimeParams } from "../../../types";
export declare const SIZES_HASH_MODULO = 2147483647;
export declare const MAX_ITEM_COUNT = 2147483647;
/**
* @public
* Core framework-agnostic model.
* Stores item sizes, positions and provides fast way to calculate offsets
*
* Core framework-agnostic model.<br />
*
* @remarks
* What it does:<br />
* - stores item sizes and positions;<br />
* - tracks elements resizing;<br />
* - provides performant way to calculate offsets;<br />
* - deals with scrolling to item index or to offset;<br />
* - emits and allows to subscribe to {@link @af-utils/virtual-core#(VirtualScrollerEvent:variable) | events}.<br />
*
* What it doesn't do:<br />
* - rendering;<br />
* - styling;<br />
* - all other framework-related stuff.
*/

@@ -13,2 +28,5 @@ declare class VirtualScroller {

private _scrollToKey;
private _desiredScrollIndex;
private _desiredScrollSmooth;
private _scrollSyncTimer;
private _alignedScrollPos;

@@ -19,3 +37,2 @@ private _scrollElementOffset;

private _availableWidgetSize;
private _scrollToTimer;
private _overscanCount;

@@ -63,3 +80,2 @@ private _estimatedItemSize;

* @remarks
*
* `window.resize` event must be used for window scroller, `ResizeObserver` must be used in other cases.

@@ -89,2 +105,5 @@ * `offsetWidth` is used as item size in horizontal mode, `offsetHeight` - in vertical.

* @returns item index;
*
* @remarks
* Time complexity: `O(log2(itemCount))`
*/

@@ -96,2 +115,5 @@ getIndex(offset: number): number;

* @returns pixel offset
*
* @remarks
* Time complexity: `O(log2(itemCount))`
*/

@@ -103,2 +125,5 @@ getOffset(index: number): number;

* @returns last cached item size
*
* @remarks
* Time complexity: `O(1)`
*/

@@ -121,20 +146,46 @@ getSize(itemIndex: number): number;

/**
* Tells model about scrollable element
* Informs model about scrollable element.
* @param element - scroller element
*
* @remarks
*
* Performs as destructor when null is passed
* will ne used as callback, so this function is bound
* Must be called with `null` before killing the instance.
*/
setScroller: (element: ScrollElement | null) => void;
setScroller(element: ScrollElement | null): void;
/**
* Should be used only when scrollable container has some "foreign" elements to properly integrate them.
* Informs model about items container element. Usually not needed.
*
* @param element - container element
*
* @remarks
* By default top/left offset between scroll container and first scrollable item is `0`.
* In this case just {@link VirtualScroller.setScroller} is needed.
* But extra element is needed when something "foreign" stands between scroll container and first scrollable item to measure distance between them.
* That extra element is represented as `ItemsContainer` on this schema:
*
* ```plaintext
* <ScrollContainer> |.|
* Some header |s|
* Another header |c|
* <ItemsContainer> |r|
* item 1 [o]
* item 2 [l]
* item 3 [l]
* ... [b]
* </ItemsContainer> |a|
* Some footer |r|
* </ScrollContainer> |.|
* ```
*
* Must be called with `null` before killing the instance.
*/
setContainer: (element: HTMLElement | null) => void;
setContainer(element: HTMLElement | null): void;
private _updateScrollerOffsetRaw;
private updateScrollerOffset;
/**
* Start observing size of `element` at `index`. Observing is finished if element is falsy.
* Start observing size of `element` at `index`. Observing is finished if element is `null`.
* @param index - item index
* @param element - element for item
*
* @remarks
* If an item was registered like `el( 5, HTMLElement )` it must be killed with `el( 5, null )` before killing the instance.
*/

@@ -144,9 +195,15 @@ el(index: number, element: HTMLElement | null): void;

/**
* Start observing size of sticky header `element`. Observing is finished if element is falsy.
* Start observing size of sticky header `element`. Observing is finished if element is `null`.
* @param element - header element
*
* @remarks
* Must be called with `null` before killing the instance.
*/
setStickyHeader(element: HTMLElement | null): void;
/**
* Start observing size of sticky footer `element`. Observing is finished if element is falsy.
* Start observing size of sticky footer `element`. Observing is finished if element is `null`.
* @param element - footer element
*
* @remarks
* Must be called with `null` before killing the instance.
*/

@@ -174,2 +231,4 @@ setStickyFooter(element: HTMLElement | null): void;

private _updateRangeFromStart;
private _killScrollEnd;
private _attemptToScrollToIndex;
/**

@@ -182,3 +241,2 @@ * Scroll to pixel offset

scrollToOffset(offset: number, smooth?: boolean): void;
private _attemptToScrollToIndex;
/**

@@ -185,0 +243,0 @@ * Scroll to item index

@@ -8,17 +8,71 @@ /**

* @public
* {@link VirtualScroller.set} argument type
* {@link VirtualScroller} parameters that may change over time.
* Used as {@link VirtualScroller.set} argument type.
*
* @remarks
* Implemented as interface for better documentation output (api-extractor)
*/
export type VirtualScrollerRuntimeParams = {
export interface VirtualScrollerRuntimeParams {
/**
* Amount of items rendered before or after visible ones.
* *
* @remarks
* Render place depends on scroll direction:<br />
* - if scrolling is done forward - these items are rendered after visible ones;<br />
* - If backward - before.
*/
overscanCount?: number;
/**
* Total items quantity
*/
itemCount?: number;
/**
* Estimated height/width of scrollable item. Orientation is determined by {@link VirtualScrollerInitialParams.horizontal}.
* *
* @remarks
* Actual size is always reported by internal `ResizeObserver`.
* Bad item size assumptions can turn into shaky scrolling experience. Accuracy here is rewarded.
*/
estimatedItemSize?: number;
};
}
/**
* @public
* {@link VirtualScroller} constructor argument type
* All {@link VirtualScroller} parameters (that may / may not change over time).
*
* @remarks
* Implemented as interface for better documentation output (api-extractor)
*/
export type VirtualScrollerInitialParams = VirtualScrollerRuntimeParams & {
export interface VirtualScrollerInitialParams extends VirtualScrollerRuntimeParams {
/**
* Scroll container orientation.
*
* @remarks
* Determines properties used for dimension/scroll calculations, for example: <br />
* - `scrollTop` / `scrollLeft`;<br />
* - `height` / `width`;<br />
* - `innerHeight` / `innerWidth`.
*/
horizontal?: boolean;
/**
* Estimated size of scroll element.
*
* @remarks
* Actual size is always reported by `ResizeObserver`,
* but this property together with {@link VirtualScrollerRuntimeParams.estimatedItemSize} and {@link VirtualScrollerRuntimeParams.overscanCount} can be used in server-side rendering.
*
* Quantity of SSR-rendered elements can be calculated this way:
*
* ```typescript
* Math.min( itemCount, Math.ceil( estimatedWidgetSize / estimatedItemSize ) + overscanCount )
* ```
*/
estimatedWidgetSize?: number;
/**
* Estimated distance between top/left edges of scrollable container and first scrollable item.
*
* @remarks
* Does not equal `0` only when scrollable container and items container are different elements.
* {@link @af-utils/virtual-core#VirtualScroller.setContainer} has more explanation.
*/
estimatedScrollElementOffset?: number;
};
}
{
"name": "@af-utils/virtual-core",
"private": false,
"version": "0.0.3",
"version": "0.0.4",
"description": "Model for rendering large scrollable data",

@@ -39,4 +39,4 @@ "repository": "https://github.com/nowaalex/af-utils.git",

"devDependencies": {
"@babel/plugin-transform-runtime": "^7.22.15",
"@babel/preset-env": "^7.22.20",
"@babel/plugin-transform-runtime": "^7.23.2",
"@babel/preset-env": "^7.23.2",
"@rollup/plugin-babel": "^6.0.4",

@@ -57,2 +57,5 @@ "@rollup/plugin-commonjs": "^25.0.5",

},
"dependencies": {
"@af-utils/scrollend-polyfill": "0.0.2"
},
"publishConfig": {

@@ -59,0 +62,0 @@ "access": "public"

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc