Skip to content

Commit

Permalink
feat: add enabled option to Virtualizer
Browse files Browse the repository at this point in the history
  • Loading branch information
sxriff committed Jun 18, 2024
1 parent 3562e06 commit 89cd776
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 45 deletions.
8 changes: 8 additions & 0 deletions docs/api/virtualizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ This function is passed the index of each item and should return the actual size

## Optional Options

### `enabled`

```tsx
enabled?: boolean
```

Set to `false` to disable scrollElement observers and reset the virtualizer's state

### `debug`

```tsx
Expand Down
162 changes: 117 additions & 45 deletions packages/virtual-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export interface VirtualizerOptions<
initialMeasurementsCache?: VirtualItem[]
lanes?: number
isScrollingResetDelay?: number
enabled?: boolean
}

export class Virtualizer<
Expand All @@ -328,6 +329,7 @@ export class Virtualizer<
options!: Required<VirtualizerOptions<TScrollElement, TItemElement>>
scrollElement: TScrollElement | null = null
targetWindow: (Window & typeof globalThis) | null = null
private enabled: boolean = true
isScrolling: boolean = false
private scrollToIndexTimeoutId: number | null = null
measurementsCache: VirtualItem[] = []
Expand Down Expand Up @@ -413,6 +415,7 @@ export class Virtualizer<
initialMeasurementsCache: [],
lanes: 1,
isScrollingResetDelay: 150,
enabled: true,
...opts,
}
}
Expand All @@ -433,62 +436,91 @@ export class Virtualizer<
}
}

private cleanup = () => {
private cleanupScrollElement = () => {
this.unsubs.filter(Boolean).forEach((d) => d!())
this.unsubs = []
this.scrollElement = null
}

private cleanup = () => {
this.cleanupScrollElement()
this.observer.disconnect()
}

private initScrollElement = () => {
if (this.scrollElement && 'ownerDocument' in this.scrollElement) {
this.targetWindow = this.scrollElement.ownerDocument.defaultView
} else {
this.targetWindow = this.scrollElement?.window ?? null
}

this._scrollToOffset(this.scrollOffset, {
adjustments: undefined,
behavior: undefined,
})

this.unsubs.push(
this.options.observeElementRect(this, (rect) => {
this.scrollRect = rect
this.notify(false, false)
}),
)

this.unsubs.push(
this.options.observeElementOffset(this, (offset, isScrolling) => {
this.scrollAdjustments = 0
this.scrollDirection = isScrolling
? this.scrollOffset < offset
? 'forward'
: 'backward'
: null
this.scrollOffset = offset

const prevIsScrolling = this.isScrolling
this.isScrolling = isScrolling

this.notify(prevIsScrolling !== isScrolling, isScrolling)
}),
)
}

private init = () => {
if (this.enabled) {
this.measureElementCache.forEach(this.observer.observe)
this.initScrollElement()
}
}

_didMount = () => {
this.measureElementCache.forEach(this.observer.observe)
this.init()

return () => {
this.observer.disconnect()
this.cleanup()
}
}

_willUpdate = () => {
const scrollElement = this.options.getScrollElement()

if (this.scrollElement !== scrollElement) {
this.cleanup()
const enabled = this.options.enabled

this.scrollElement = scrollElement
if (this.enabled !== enabled) {
this.enabled = enabled

if (this.scrollElement && 'ownerDocument' in this.scrollElement) {
this.targetWindow = this.scrollElement.ownerDocument.defaultView
if (enabled) {
this.init()
} else {
this.targetWindow = this.scrollElement?.window ?? null
this.cleanup()
}

this._scrollToOffset(this.scrollOffset, {
adjustments: undefined,
behavior: undefined,
})
this.notify(true, false)
return
}

this.unsubs.push(
this.options.observeElementRect(this, (rect) => {
this.scrollRect = rect
this.notify(false, false)
}),
)
const scrollElement = this.options.getScrollElement()

this.unsubs.push(
this.options.observeElementOffset(this, (offset, isScrolling) => {
this.scrollAdjustments = 0
this.scrollDirection = isScrolling
? this.scrollOffset < offset
? 'forward'
: 'backward'
: null
this.scrollOffset = offset

const prevIsScrolling = this.isScrolling
this.isScrolling = isScrolling

this.notify(prevIsScrolling !== isScrolling, isScrolling)
}),
)
if (this.enabled && this.scrollElement !== scrollElement) {
this.cleanupScrollElement()
this.scrollElement = scrollElement
this.initScrollElement()
}
}

Expand Down Expand Up @@ -559,8 +591,14 @@ export class Virtualizer<
}

private getMeasurements = memo(
() => [this.getMeasurementOptions(), this.itemSizeCache],
({ count, paddingStart, scrollMargin, getItemKey }, itemSizeCache) => {
() => [this.getMeasurementOptions(), this.itemSizeCache, this.enabled],
(
{ count, paddingStart, scrollMargin, getItemKey },
itemSizeCache,
enabled,
) => {
if (!enabled) return []

const min =
this.pendingMeasuredCacheIndexes.length > 0
? Math.min(...this.pendingMeasuredCacheIndexes)
Expand Down Expand Up @@ -614,8 +652,15 @@ export class Virtualizer<
)

calculateRange = memo(
() => [this.getMeasurements(), this.getSize(), this.scrollOffset],
(measurements, outerSize, scrollOffset) => {
() => [
this.getMeasurements(),
this.getSize(),
this.scrollOffset,
this.enabled,
],
(measurements, outerSize, scrollOffset, enabled) => {
if (!enabled) return null

return (this.range =
measurements.length > 0 && outerSize > 0
? calculateRange({
Expand All @@ -637,8 +682,11 @@ export class Virtualizer<
this.calculateRange(),
this.options.overscan,
this.options.count,
this.enabled,
],
(rangeExtractor, range, overscan, count) => {
(rangeExtractor, range, overscan, count, enabled) => {
if (!enabled) return []

return range === null
? []
: rangeExtractor({
Expand All @@ -655,6 +703,8 @@ export class Virtualizer<
)

indexFromElement = (node: TItemElement) => {
if (!this.enabled) return -1

const attributeName = this.options.indexAttribute
const indexStr = node.getAttribute(attributeName)

Expand All @@ -672,6 +722,8 @@ export class Virtualizer<
node: TItemElement,
entry: ResizeObserverEntry | undefined,
) => {
if (!this.enabled) return

const item = this.measurementsCache[this.indexFromElement(node)]

if (!item || !node.isConnected) {
Expand Down Expand Up @@ -700,6 +752,8 @@ export class Virtualizer<
}

resizeItem = (item: VirtualItem, size: number) => {
if (!this.enabled) return

const itemSize = this.itemSizeCache.get(item.key) ?? item.size
const delta = size - itemSize

Expand Down Expand Up @@ -727,16 +781,18 @@ export class Virtualizer<
}

measureElement = (node: TItemElement | null) => {
if (!node) {
if (!node || !this.enabled) {
return
}

this._measureElement(node, undefined)
}

getVirtualItems = memo(
() => [this.getIndexes(), this.getMeasurements()],
(indexes, measurements) => {
() => [this.getIndexes(), this.getMeasurements(), this.enabled],
(indexes, measurements, enabled) => {
if (!enabled) return []

const virtualItems: VirtualItem[] = []

for (let k = 0, len = indexes.length; k < len; k++) {
Expand All @@ -755,6 +811,8 @@ export class Virtualizer<
)

getVirtualItemForOffset = (offset: number) => {
if (!this.enabled) return null

const measurements = this.getMeasurements()

return notUndefined(
Expand All @@ -770,6 +828,8 @@ export class Virtualizer<
}

getOffsetForAlignment = (toOffset: number, align: ScrollAlignment) => {
if (!this.enabled) return 0

const size = this.getSize()

if (align === 'auto') {
Expand Down Expand Up @@ -805,6 +865,8 @@ export class Virtualizer<
}

getOffsetForIndex = (index: number, align: ScrollAlignment = 'auto') => {
if (!this.enabled) return [0, align] as const

index = Math.max(0, Math.min(index, this.options.count - 1))

const measurement = notUndefined(this.getMeasurements()[index])
Expand Down Expand Up @@ -846,6 +908,8 @@ export class Virtualizer<
toOffset: number,
{ align = 'start', behavior }: ScrollToOffsetOptions = {},
) => {
if (!this.enabled) return

this.cancelScrollToIndex()

if (behavior === 'smooth' && this.isDynamicMode()) {
Expand All @@ -864,6 +928,8 @@ export class Virtualizer<
index: number,
{ align: initialAlign = 'auto', behavior }: ScrollToIndexOptions = {},
) => {
if (!this.enabled) return

index = Math.max(0, Math.min(index, this.options.count - 1))

this.cancelScrollToIndex()
Expand Down Expand Up @@ -900,6 +966,8 @@ export class Virtualizer<
}

scrollBy = (delta: number, { behavior }: ScrollToOffsetOptions = {}) => {
if (!this.enabled) return

this.cancelScrollToIndex()

if (behavior === 'smooth' && this.isDynamicMode()) {
Expand All @@ -915,6 +983,8 @@ export class Virtualizer<
}

getTotalSize = () => {
if (!this.enabled) return 0

const measurements = this.getMeasurements()

let end: number
Expand Down Expand Up @@ -948,6 +1018,8 @@ export class Virtualizer<
}

measure = () => {
if (!this.enabled) return

this.itemSizeCache = new Map()
this.options.onChange?.(this, false)
}
Expand Down

0 comments on commit 89cd776

Please sign in to comment.