Skip to content

Commit

Permalink
feat: add measureElement on virtual item, via measureElementCache whe…
Browse files Browse the repository at this point in the history
…n we can't relay on node.isConnected
  • Loading branch information
piecyk committed Jun 2, 2024
1 parent c3410c2 commit 37817a9
Showing 1 changed file with 65 additions and 23 deletions.
88 changes: 65 additions & 23 deletions packages/virtual-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ export interface Range {

type Key = number | string

export interface VirtualItem {
export interface VirtualItem<TItemElement extends Element> {
key: Key
index: number
start: number
end: number
size: number
lane: number
measureElement: (node: TItemElement | null | undefined) => void
}

export interface Rect {
Expand Down Expand Up @@ -295,7 +296,7 @@ export interface VirtualizerOptions<
scrollMargin?: number
gap?: number
indexAttribute?: string
initialMeasurementsCache?: VirtualItem[]
initialMeasurementsCache?: VirtualItem<TItemElement>[]
lanes?: number
isScrollingResetDelay?: number
}
Expand All @@ -309,7 +310,7 @@ export class Virtualizer<
scrollElement: TScrollElement | null = null
isScrolling: boolean = false
private scrollToIndexTimeoutId: ReturnType<typeof setTimeout> | null = null
measurementsCache: VirtualItem[] = []
measurementsCache: VirtualItem<TItemElement>[] = []
private itemSizeCache = new Map<Key, number>()
private pendingMeasuredCacheIndexes: number[] = []
scrollRect: Rect
Expand All @@ -319,11 +320,15 @@ export class Virtualizer<
shouldAdjustScrollPositionOnItemSizeChange:
| undefined
| ((
item: VirtualItem,
item: VirtualItem<TItemElement>,
delta: number,
instance: Virtualizer<TScrollElement, TItemElement>,
) => boolean)
measureElementCache = new Map<Key, TItemElement>()
elementCache = new Map<Key, TItemElement>()
measureElementCache = new Map<
number,
(node: TItemElement | null | undefined) => void
>()
private observer = (() => {
let _ro: ResizeObserver | null = null

Expand Down Expand Up @@ -417,7 +422,7 @@ export class Virtualizer<
}

_didMount = () => {
this.measureElementCache.forEach(this.observer.observe)
this.elementCache.forEach(this.observer.observe)
return () => {
this.observer.disconnect()
this.cleanup()
Expand Down Expand Up @@ -489,11 +494,11 @@ export class Virtualizer<
)

private getFurthestMeasurement = (
measurements: VirtualItem[],
measurements: VirtualItem<TItemElement>[],
index: number,
) => {
const furthestMeasurementsFound = new Map<number, true>()
const furthestMeasurements = new Map<number, VirtualItem>()
const furthestMeasurements = new Map<number, VirtualItem<TItemElement>>()
for (let m = index - 1; m >= 0; m--) {
const measurement = measurements[m]!

Expand Down Expand Up @@ -541,6 +546,39 @@ export class Virtualizer<
const measurements = this.measurementsCache.slice(0, min)

for (let i = min; i < count; i++) {
let measureElement = this.measureElementCache.get(i)

if (!measureElement) {
measureElement = (node: TItemElement | null | undefined) => {
const key = getItemKey(i)
const prevNode = this.elementCache.get(key)

if (!node) {
if (prevNode) {
this.observer.unobserve(prevNode)
this.elementCache.delete(key)
}
return
}

if (prevNode !== node) {
if (prevNode) {
this.observer.unobserve(prevNode)
}
this.observer.observe(node)
this.elementCache.set(key, node)
}

if (node.isConnected) {
this.resizeItem(
i,
this.options.measureElement(node, undefined, this),
)
}
}
this.measureElementCache.set(i, measureElement)
}

const key = getItemKey(i)

const furthestMeasurement =
Expand Down Expand Up @@ -571,6 +609,7 @@ export class Virtualizer<
end,
key,
lane,
measureElement,
}
}

Expand Down Expand Up @@ -643,34 +682,37 @@ export class Virtualizer<
node: TItemElement,
entry: ResizeObserverEntry | undefined,
) => {
const item = this.measurementsCache[this.indexFromElement(node)]
const i = this.indexFromElement(node)
const item = this.measurementsCache[i]

if (!item || !node.isConnected) {
this.measureElementCache.forEach((cached, key) => {
this.elementCache.forEach((cached, key) => {
if (cached === node) {
this.observer.unobserve(node)
this.measureElementCache.delete(key)
this.elementCache.delete(key)
}
})
return
}

const prevNode = this.measureElementCache.get(item.key)
const prevNode = this.elementCache.get(item.key)

if (prevNode !== node) {
if (prevNode) {
this.observer.unobserve(prevNode)
}
this.observer.observe(node)
this.measureElementCache.set(item.key, node)
this.elementCache.set(item.key, node)
}

const measuredItemSize = this.options.measureElement(node, entry, this)

this.resizeItem(item, measuredItemSize)
this.resizeItem(i, this.options.measureElement(node, entry, this))
}

resizeItem = (item: VirtualItem, size: number) => {
resizeItem = (index: number, size: number) => {
const item = this.measurementsCache[index]
if (!item) {
return
}
const itemSize = this.itemSizeCache.get(item.key) ?? item.size
const delta = size - itemSize

Expand All @@ -697,7 +739,7 @@ export class Virtualizer<
}
}

measureElement = (node: TItemElement | null) => {
measureElement = (node: TItemElement | null | undefined) => {
if (!node) {
return
}
Expand All @@ -708,7 +750,7 @@ export class Virtualizer<
getVirtualItems = memo(
() => [this.getIndexes(), this.getMeasurements()],
(indexes, measurements) => {
const virtualItems: VirtualItem[] = []
const virtualItems: VirtualItem<TItemElement>[] = []

for (let k = 0, len = indexes.length; k < len; k++) {
const i = indexes[k]!
Expand Down Expand Up @@ -804,7 +846,7 @@ export class Virtualizer<
return [this.getOffsetForAlignment(toOffset, align), align] as const
}

private isDynamicMode = () => this.measureElementCache.size > 0
private isDynamicMode = () => this.elementCache.size > 0

private cancelScrollToIndex = () => {
if (this.scrollToIndexTimeoutId !== null) {
Expand Down Expand Up @@ -853,7 +895,7 @@ export class Virtualizer<
this.scrollToIndexTimeoutId = setTimeout(() => {
this.scrollToIndexTimeoutId = null

const elementInDOM = this.measureElementCache.has(
const elementInDOM = this.elementCache.has(
this.options.getItemKey(index),
)

Expand Down Expand Up @@ -950,12 +992,12 @@ const findNearestBinarySearch = (
}
}

function calculateRange({
function calculateRange<TItemElement extends Element>({
measurements,
outerSize,
scrollOffset,
}: {
measurements: VirtualItem[]
measurements: VirtualItem<TItemElement>[]
outerSize: number
scrollOffset: number
}) {
Expand Down

0 comments on commit 37817a9

Please sign in to comment.