@af-utils/virtual-core
Advanced tools
Comparing version 0.0.3 to 0.0.4
@@ -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 { } |
253
lib/index.js
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
130244
1242
1
6
+ Added@af-utils/scrollend-polyfill@0.0.2(transitive)