Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add enabled option to Virtualizer #724

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
sxriff marked this conversation as resolved.
Show resolved Hide resolved
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