react-virtuoso
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -10,2 +10,3 @@ interface NodeData<T> { | ||
} | ||
declare type FindCallback<T> = (value: T) => 1 | 0 | -1; | ||
export declare type NodeIterator<T> = IterableIterator<NodeData<T>>; | ||
@@ -17,4 +18,6 @@ export declare type RangeIterator<T> = IterableIterator<Range<T>>; | ||
private constructor(); | ||
find(key: number): T | void; | ||
find(key: number): T | undefined; | ||
findMax(key: number): number; | ||
findMaxValue(key: number): T; | ||
findWith(callback: FindCallback<T>): [number, T] | void; | ||
insert(key: number, value: T): AATree<T>; | ||
@@ -21,0 +24,0 @@ remove(key: number): AATree<T>; |
export * from './Virtuoso'; | ||
export * from './GroupedVirtuoso'; |
@@ -17,7 +17,18 @@ import { AATree } from './AATree'; | ||
private constructor(); | ||
empty(): boolean; | ||
insert(start: number, end: number, size: number): OffsetList; | ||
insertException(index: number, value: number): OffsetList; | ||
offsetOf(index: number): number; | ||
itemAt(index: number): Item; | ||
indexRange(startIndex: number, endIndex: number): Item[]; | ||
range(startOffset: number, endOffset: number, minIndex?: number, maxIndex?: number): Item[]; | ||
total(endIndex: number): number; | ||
getOffsets(indices: number[]): IndexList; | ||
} | ||
export declare class IndexList { | ||
tree: AATree<number>; | ||
constructor(tree: AATree<number>); | ||
findMaxValue(offset: number): number; | ||
empty(): boolean; | ||
} | ||
export {}; |
@@ -11,2 +11,4 @@ 'use strict'; | ||
const VirtuosoContext = React.createContext(undefined); | ||
class NilNode { | ||
@@ -28,5 +30,11 @@ constructor() { | ||
} | ||
findWith() { | ||
return; | ||
} | ||
findMax() { | ||
return -Infinity; | ||
} | ||
findMaxValue() { | ||
return; | ||
} | ||
insert(key, value) { | ||
@@ -63,2 +71,7 @@ // eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
Object.freeze(NIL_NODE); | ||
class UnreachableCaseError extends Error { | ||
constructor(val) { | ||
super(`Unreachable case: ${val}`); | ||
} | ||
} | ||
class NonNilNode { | ||
@@ -115,2 +128,15 @@ constructor({ key, value, level, left = NIL_NODE, right = NIL_NODE }) { | ||
} | ||
findWith(callback) { | ||
const result = callback(this.value); | ||
switch (result) { | ||
case -1: | ||
return this.left.findWith(callback); | ||
case 0: | ||
return [this.key, this.value]; | ||
case 1: | ||
return this.right.findWith(callback); | ||
default: | ||
throw new UnreachableCaseError(result); | ||
} | ||
} | ||
findMax(key) { | ||
@@ -131,2 +157,17 @@ if (this.key === key) { | ||
} | ||
findMaxValue(key) { | ||
if (this.key === key) { | ||
return this.value; | ||
} | ||
if (this.key < key) { | ||
const rightValue = this.right.findMaxValue(key); | ||
if (rightValue === undefined) { | ||
return this.value; | ||
} | ||
else { | ||
return rightValue; | ||
} | ||
} | ||
return this.left.findMaxValue(key); | ||
} | ||
insert(key, value) { | ||
@@ -324,2 +365,11 @@ if (key === this.key) { | ||
} | ||
findMaxValue(key) { | ||
if (this.empty()) { | ||
throw new Error('Searching for max value in an empty tree'); | ||
} | ||
return this.root.findMaxValue(key); | ||
} | ||
findWith(callback) { | ||
return this.root.findWith(callback); | ||
} | ||
insert(key, value) { | ||
@@ -376,2 +426,5 @@ return new AATree(this.root.insert(key, value)); | ||
} | ||
empty() { | ||
return this.rangeTree.empty(); | ||
} | ||
insert(start, end, size) { | ||
@@ -408,3 +461,3 @@ let tree = this.rangeTree; | ||
if (rangeEnd > end && end >= rangeStart) { | ||
if (rangeValue !== size) { | ||
if (rangeValue !== size && !isNaN(rangeValue)) { | ||
tree = tree.insert(end + 1, rangeValue); | ||
@@ -419,2 +472,34 @@ } | ||
} | ||
insertException(index, value) { | ||
if (this.empty()) { | ||
return new OffsetList(this.rangeTree.insert(1, NaN).insert(index, value)); | ||
} | ||
else { | ||
return this.insert(index, index, value); | ||
} | ||
} | ||
offsetOf(index) { | ||
if (this.offsetTree.empty()) { | ||
return 0; | ||
} | ||
const find = (value) => { | ||
if (value.startIndex > index) | ||
return -1; | ||
if (value.endIndex < index) | ||
return 1; | ||
return 0; | ||
}; | ||
const offsetRange = this.offsetTree.findWith(find); | ||
if (offsetRange) { | ||
const [offset, { startIndex, size }] = offsetRange; | ||
return offset + (index - startIndex) * size; | ||
} | ||
else { | ||
throw new Error(`Requested offset outside of the known ones, index: ${index}`); | ||
} | ||
} | ||
itemAt(index) { | ||
const size = this.rangeTree.findMaxValue(index); | ||
return { index, size, offset: NaN }; | ||
} | ||
indexRange(startIndex, endIndex) { | ||
@@ -455,3 +540,11 @@ if (endIndex === 0) { | ||
} | ||
startIndex = Math.max(minIndex, startIndex); | ||
if (startIndex < minIndex) { | ||
offset += (minIndex - startIndex) * size; | ||
startIndex = minIndex; | ||
} | ||
// we don't know the size of this range - terminate with a probe item | ||
if (isNaN(size)) { | ||
result.push({ index: startIndex, size: 0, offset }); | ||
return result; | ||
} | ||
endIndex = Math.min(endIndex, maxIndex); | ||
@@ -477,23 +570,84 @@ for (let i = startIndex; i <= endIndex; i++) { | ||
} | ||
getOffsets(indices) { | ||
let tree = AATree.empty(); | ||
indices.forEach(index => { | ||
const offset = this.offsetOf(index); | ||
tree = tree.insert(offset, index); | ||
}); | ||
return new IndexList(tree); | ||
} | ||
} | ||
class IndexList { | ||
constructor(tree) { | ||
this.tree = tree; | ||
} | ||
findMaxValue(offset) { | ||
return this.tree.findMaxValue(offset); | ||
} | ||
empty() { | ||
return this.tree.empty(); | ||
} | ||
} | ||
class StubIndexTransposer { | ||
transpose(items) { | ||
return items.map(item => { | ||
return { | ||
groupIndex: 0, | ||
index: item.index, | ||
offset: item.offset, | ||
size: item.size, | ||
transposedIndex: item.index, | ||
type: 'item', | ||
}; | ||
}); | ||
} | ||
} | ||
class GroupIndexTransposer { | ||
constructor(counts) { | ||
this.count = counts.reduce((acc, groupCount) => acc + groupCount + 1, 0); | ||
let tree = AATree.empty(); | ||
let groupIndex = 0; | ||
let total = 0; | ||
for (let groupCount of counts) { | ||
tree = tree.insert(total, [groupIndex, total]); | ||
groupIndex++; | ||
total += groupCount + 1; | ||
} | ||
this.tree = tree; | ||
} | ||
totalCount() { | ||
return this.count; | ||
} | ||
transpose(items) { | ||
return items.map(item => { | ||
const groupMatch = this.tree.find(item.index); | ||
if (groupMatch) { | ||
return { | ||
groupIndex: groupMatch[0], | ||
index: item.index, | ||
offset: item.offset, | ||
size: item.size, | ||
type: 'group', | ||
}; | ||
} | ||
const [groupIndex] = this.tree.findMaxValue(item.index); | ||
return { | ||
groupIndex: groupIndex, | ||
index: item.index, | ||
offset: item.offset, | ||
size: item.size, | ||
transposedIndex: item.index - groupIndex - 1, | ||
type: 'item', | ||
}; | ||
}); | ||
} | ||
groupIndices() { | ||
return this.tree.keys(); | ||
} | ||
} | ||
const getListTop = (items) => (items.length > 0 ? items[0].offset : 0); | ||
const mapToTotal = ([offsetList, totalCount]) => offsetList.total(totalCount - 1); | ||
const listScanner = overscan => (items, [[viewportHeight, scrollTop, topListHeight, listHeight, footerHeight, minIndex, totalCount], offsetList]) => { | ||
const listTop = getListTop(items); | ||
const listBottom = listTop - scrollTop + listHeight - footerHeight - topListHeight; | ||
const maxIndex = Math.max(totalCount - 1, 0); | ||
if (listBottom < viewportHeight) { | ||
const startOffset = Math.max(scrollTop + topListHeight, topListHeight); | ||
const endOffset = scrollTop + viewportHeight + overscan * 2 - 1; | ||
return offsetList.range(startOffset, endOffset, minIndex, maxIndex); | ||
} | ||
if (listTop > scrollTop + topListHeight) { | ||
const startOffset = Math.max(scrollTop + topListHeight - overscan * 2, topListHeight); | ||
const endOffset = scrollTop + viewportHeight - 1; | ||
return offsetList.range(startOffset, endOffset, minIndex, maxIndex); | ||
} | ||
return items; | ||
}; | ||
const VirtuosoStore = ({ overscan = 0, totalCount, topItems = 0, itemHeight }) => { | ||
const VirtuosoStore = ({ overscan = 0, totalCount = 0, itemHeight }) => { | ||
const viewportHeight$ = new rxjs.BehaviorSubject(0); | ||
@@ -505,5 +659,7 @@ const listHeight$ = new rxjs.BehaviorSubject(0); | ||
const totalCount$ = new rxjs.BehaviorSubject(totalCount); | ||
const topItemCount$ = new rxjs.BehaviorSubject(topItems); | ||
const groupCounts$ = new rxjs.Subject(); | ||
const topItemCount$ = new rxjs.Subject(); | ||
const isScrolling$ = new rxjs.BehaviorSubject(false); | ||
let initialOffsetList = OffsetList.create(); | ||
const stickyItems$ = new rxjs.BehaviorSubject([]); | ||
if (itemHeight) { | ||
@@ -514,4 +670,9 @@ initialOffsetList = initialOffsetList.insert(0, 0, itemHeight); | ||
if (!itemHeight) { | ||
itemHeights$.pipe(operators.withLatestFrom(offsetList$)).subscribe(([heights, offsetList]) => { | ||
const newList = heights.reduce((list, { start, end, size }) => list.insert(start, end, size), offsetList); | ||
itemHeights$.pipe(operators.withLatestFrom(offsetList$, stickyItems$)).subscribe(([heights, offsetList, stickyItems]) => { | ||
const newList = heights.reduce((list, { start, end, size }) => { | ||
if (start === end && stickyItems.indexOf(start) > -1) { | ||
return list.insertException(start, size); | ||
} | ||
return list.insert(start, end, size); | ||
}, offsetList); | ||
if (newList !== offsetList) { | ||
@@ -522,10 +683,52 @@ offsetList$.next(newList); | ||
} | ||
let transposer = new StubIndexTransposer(); | ||
const listScanner = overscan => (items, [[viewportHeight, scrollTop, topListHeight, listHeight, footerHeight, minIndex, totalCount], offsetList]) => { | ||
const listTop = getListTop(items); | ||
const listBottom = listTop - scrollTop + listHeight - footerHeight - topListHeight; | ||
const maxIndex = Math.max(totalCount - 1, 0); | ||
const topIndexOutOfRange = items.length > 0 && items[0].index < minIndex; | ||
if (listBottom < viewportHeight || topIndexOutOfRange) { | ||
const startOffset = Math.max(scrollTop + topListHeight, topListHeight); | ||
const endOffset = scrollTop + viewportHeight + overscan * 2 - 1; | ||
return transposer.transpose(offsetList.range(startOffset, endOffset, minIndex, maxIndex)); | ||
} | ||
if (listTop > scrollTop + topListHeight) { | ||
const startOffset = Math.max(scrollTop + topListHeight - overscan * 2, topListHeight); | ||
const endOffset = scrollTop + viewportHeight - 1; | ||
return transposer.transpose(offsetList.range(startOffset, endOffset, minIndex, maxIndex)); | ||
} | ||
return items; | ||
}; | ||
groupCounts$.subscribe(counts => { | ||
transposer = new GroupIndexTransposer(counts); | ||
totalCount$.next(transposer.totalCount()); | ||
stickyItems$.next(transposer.groupIndices()); | ||
}); | ||
const totalListHeight$ = rxjs.combineLatest(offsetList$, totalCount$).pipe(operators.map(mapToTotal)); | ||
const totalHeight$ = rxjs.combineLatest(totalListHeight$, footerHeight$).pipe(operators.map(([totalListHeight, footerHeight]) => totalListHeight + footerHeight)); | ||
const topList$ = rxjs.combineLatest(offsetList$, topItemCount$, totalCount$).pipe(operators.map(([offsetList, topItemCount, totalCount]) => { | ||
const stickyItemsIndexList$ = rxjs.combineLatest(offsetList$, stickyItems$).pipe(operators.map(([offsetList, stickyItems]) => { | ||
return offsetList.getOffsets(stickyItems); | ||
})); | ||
const topList$ = new rxjs.BehaviorSubject([]); | ||
rxjs.combineLatest(offsetList$, topItemCount$, totalCount$) | ||
.pipe(operators.filter(params => params[1] > 0), operators.map(([offsetList, topItemCount, totalCount]) => { | ||
const endIndex = Math.max(0, Math.min(topItemCount - 1, totalCount)); | ||
return offsetList.indexRange(0, endIndex); | ||
})); | ||
return transposer.transpose(offsetList.indexRange(0, endIndex)); | ||
})) | ||
.subscribe(topList$); | ||
rxjs.combineLatest(offsetList$, stickyItemsIndexList$, scrollTop$) | ||
.pipe(operators.filter(params => !params[1].empty() && !params[0].empty()), operators.withLatestFrom(topList$), operators.map(([[offsetList, stickyItemsIndexList, scrollTop], topList]) => { | ||
const currentStickyItem = stickyItemsIndexList.findMaxValue(scrollTop); | ||
if (topList.length === 1 && topList[0].index === currentStickyItem) { | ||
return topList; | ||
} | ||
const item = offsetList.itemAt(currentStickyItem); | ||
return transposer.transpose([item]); | ||
}), operators.distinctUntilChanged()) | ||
.subscribe(topList$); | ||
const topListHeight$ = topList$.pipe(operators.map(items => items.reduce((total, item) => total + item.size, 0)), operators.distinctUntilChanged(), operators.auditTime(0)); | ||
const list$ = rxjs.combineLatest(viewportHeight$.pipe(operators.distinctUntilChanged()), scrollTop$.pipe(operators.distinctUntilChanged()), topListHeight$.pipe(operators.distinctUntilChanged()), listHeight$.pipe(operators.distinctUntilChanged()), footerHeight$.pipe(operators.distinctUntilChanged()), topItemCount$, totalCount$).pipe(operators.withLatestFrom(offsetList$), operators.scan(listScanner(overscan), []), operators.distinctUntilChanged()); | ||
const minListIndex$ = topList$.pipe(operators.map(topList => { | ||
return topList.length && topList[topList.length - 1].index + 1; | ||
}), operators.distinctUntilChanged()); | ||
const list$ = rxjs.combineLatest(viewportHeight$.pipe(operators.distinctUntilChanged()), scrollTop$.pipe(operators.distinctUntilChanged()), topListHeight$.pipe(operators.distinctUntilChanged()), listHeight$.pipe(operators.distinctUntilChanged()), footerHeight$.pipe(operators.distinctUntilChanged()), minListIndex$, totalCount$).pipe(operators.withLatestFrom(offsetList$), operators.scan(listScanner(overscan), []), operators.distinctUntilChanged()); | ||
const endReached$ = list$.pipe(operators.map(items => (items.length ? items[items.length - 1].index : 0)), operators.scan((prev, current) => Math.max(prev, current)), operators.distinctUntilChanged()); | ||
@@ -539,2 +742,3 @@ const listOffset$ = rxjs.combineLatest(list$, scrollTop$, topListHeight$).pipe(operators.map(([items, scrollTop, topListHeight]) => getListTop(items) - scrollTop - topListHeight)); | ||
.subscribe(isScrolling$); | ||
const subscriptions = new rxjs.Subscription(); | ||
return { | ||
@@ -548,2 +752,4 @@ // input | ||
viewportHeight$, | ||
topItemCount$, | ||
groupCounts$, | ||
// output | ||
@@ -556,7 +762,7 @@ list$, | ||
isScrolling$, | ||
subscriptions, | ||
stickyItems$, | ||
}; | ||
}; | ||
const VirtuosoContext = React.createContext(undefined); | ||
const useHeight = (observer$, onMount = null) => { | ||
@@ -610,60 +816,86 @@ const ref = React.useRef(null); | ||
const buildSizes = items => { | ||
const results = []; | ||
for (const item of items) { | ||
const index = parseInt(item.dataset.index); | ||
const size = item.offsetHeight; | ||
if (results.length === 0 || results[results.length - 1].size !== size) { | ||
results.push({ start: index, end: index, size }); | ||
class ItemHeightPublisher { | ||
constructor(itemHeights$) { | ||
this.itemElements = []; | ||
this.trackRef = (ref) => { | ||
if (ref) { | ||
this.itemElements.push(ref); | ||
} | ||
}; | ||
this.itemHeights$ = itemHeights$; | ||
} | ||
publishSizes(items) { | ||
const results = []; | ||
for (const item of items) { | ||
const index = parseInt(item.dataset.index); | ||
const size = item.offsetHeight; | ||
if (results.length === 0 || results[results.length - 1].size !== size) { | ||
results.push({ start: index, end: index, size }); | ||
} | ||
else { | ||
results[results.length - 1].end++; | ||
} | ||
} | ||
else { | ||
results[results.length - 1].end++; | ||
} | ||
this.itemHeights$.next(results); | ||
} | ||
return results; | ||
}; | ||
const itemRenderer = ({ items, itemAttributes, transform, render }) => { | ||
const style = { | ||
transform, | ||
zIndex: transform !== '' ? 2 : undefined, | ||
position: transform !== '' ? 'relative' : undefined, | ||
}; | ||
init() { | ||
this.observer = new ResizeObserver((entries) => { | ||
this.publishSizes(entries.map(({ target }) => target)); | ||
}); | ||
this.publishSizes(this.itemElements); | ||
this.itemElements.map(item => { | ||
this.observer.observe(item); | ||
}); | ||
} | ||
destroy() { | ||
this.itemElements = []; | ||
this.observer.disconnect(); | ||
} | ||
getItemAttributes() { | ||
return (item) => { | ||
return { | ||
'data-index': item.index, | ||
'data-known-size': item.size, | ||
ref: this.trackRef, | ||
}; | ||
}; | ||
} | ||
} | ||
const itemRenderer = ({ items, itemAttributes, render, getStyle }) => { | ||
return items.map(item => { | ||
return (React__default.createElement("div", Object.assign({ key: item.index }, itemAttributes && itemAttributes(item), { style: style }), render(item.index))); | ||
return (React__default.createElement("div", Object.assign({ key: item.index }, itemAttributes && itemAttributes(item), { style: getStyle(item.index) }), render(item))); | ||
}); | ||
}; | ||
const VirtuosoList = React__default.memo(({ list$, transform = '', item: itemRenderProp }) => { | ||
const VirtuosoVariableList = React__default.memo(({ items, render, getStyle }) => { | ||
const { itemHeights$ } = React.useContext(VirtuosoContext); | ||
const items = useObservable(list$, []); | ||
const itemRefs = React.useRef([]); | ||
const heightPublisher = React.useRef(new ItemHeightPublisher(itemHeights$)); | ||
React.useLayoutEffect(() => { | ||
let observer = new ResizeObserver((entries) => { | ||
itemHeights$.next(buildSizes(entries.map(({ target }) => target))); | ||
}); | ||
itemHeights$.next(buildSizes(itemRefs.current)); | ||
itemRefs.current.map(item => { | ||
observer.observe(item); | ||
}); | ||
heightPublisher.current.init(); | ||
return () => { | ||
itemRefs.current = []; | ||
observer.disconnect(); | ||
heightPublisher.current.destroy(); | ||
}; | ||
}, [items]); | ||
const itemCallbackRef = React.useCallback(ref => { | ||
if (ref) { | ||
itemRefs.current.push(ref); | ||
} | ||
}, [items]); | ||
const itemAttributes = (item) => { | ||
return { | ||
'data-index': item.index, | ||
'data-known-size': item.size, | ||
ref: itemCallbackRef, | ||
}; | ||
}; | ||
return React__default.createElement(React__default.Fragment, null, itemRenderer({ items, transform, render: itemRenderProp, itemAttributes })); | ||
return React__default.createElement(React__default.Fragment, null, itemRenderer({ items, render, itemAttributes: heightPublisher.current.getItemAttributes(), getStyle })); | ||
}); | ||
const VirtuosoFixedList = React__default.memo(({ list$, transform = '', item: itemRenderProp }) => { | ||
const VirtuosoStaticList = React__default.memo(({ items, render, getStyle }) => { | ||
return React__default.createElement(React__default.Fragment, null, itemRenderer({ items, render, getStyle })); | ||
}); | ||
const VirtuosoList = React__default.memo(({ list$, transform = '', render, fixedItemHeight }) => { | ||
const { stickyItems$ } = React.useContext(VirtuosoContext); | ||
const items = useObservable(list$, []); | ||
return React__default.createElement(React__default.Fragment, null, itemRenderer({ items, transform, render: itemRenderProp })); | ||
const stickyItems = useObservable(stickyItems$, []); | ||
const getStyle = React.useCallback((index) => { | ||
const pinned = stickyItems.some(stickyItemIndex => stickyItemIndex === index); | ||
const style = { | ||
transform, | ||
zIndex: pinned ? 2 : undefined, | ||
position: pinned ? 'relative' : undefined, | ||
}; | ||
return style; | ||
}, [stickyItems, transform]); | ||
if (items.length === 0) { | ||
return null; | ||
} | ||
return fixedItemHeight ? (React__default.createElement(VirtuosoStaticList, Object.assign({}, { items, render, getStyle }))) : (React__default.createElement(VirtuosoVariableList, Object.assign({}, { items, render, getStyle }))); | ||
}); | ||
@@ -686,3 +918,3 @@ | ||
}; | ||
const VirtuosoView = ({ style, footer, item, topItemCount, fixedItemHeight }) => { | ||
const VirtuosoView = ({ style, footer, item, fixedItemHeight }) => { | ||
const { listHeight$, viewportHeight$, listOffset$, list$, topList$ } = React.useContext(VirtuosoContext); | ||
@@ -702,4 +934,4 @@ const listOffset = useObservable(listOffset$, 0); | ||
React__default.createElement("div", { ref: listCallbackRef }, | ||
topItemCount > 0 && React__default.createElement(VirtuosoList, { "list$": topList$, transform: topTransform, item: item }), | ||
fixedItemHeight ? (React__default.createElement(VirtuosoFixedList, { "list$": list$, item: item })) : (React__default.createElement(VirtuosoList, { "list$": list$, item: item })), | ||
React__default.createElement(VirtuosoList, { "list$": topList$, transform: topTransform, render: item, fixedItemHeight: fixedItemHeight }), | ||
React__default.createElement(VirtuosoList, { "list$": list$, render: item, fixedItemHeight: fixedItemHeight }), | ||
footer && React__default.createElement(VirtuosoFooter, { footer: footer })))), | ||
@@ -709,27 +941,67 @@ React__default.createElement(VirtuosoFiller, null))); | ||
const VirtuosoPresentation = ({ contextValue, style, item, footer, itemHeight, }) => { | ||
return (React__default.createElement(VirtuosoContext.Provider, { value: contextValue }, | ||
React__default.createElement(VirtuosoView, { style: style || {}, item: item, footer: footer, fixedItemHeight: itemHeight !== undefined }))); | ||
}; | ||
class Virtuoso extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.state = VirtuosoStore(props); | ||
this.itemRenderer = (item) => { | ||
return this.props.item(item.index); | ||
}; | ||
this.state = Virtuoso.getDerivedStateFromProps(this.props, VirtuosoStore(props)); | ||
} | ||
static getDerivedStateFromProps(props, state) { | ||
state.subscriptions.unsubscribe(); | ||
const nextSubscriptions = new rxjs.Subscription(); | ||
if (props.endReached) { | ||
this.state.endReached$.subscribe(props.endReached); | ||
nextSubscriptions.add(state.endReached$.subscribe(props.endReached)); | ||
} | ||
if (props.scrollingStateChange) { | ||
this.state.isScrolling$.subscribe(props.scrollingStateChange); | ||
nextSubscriptions.add(state.isScrolling$.subscribe(props.scrollingStateChange)); | ||
} | ||
if (props.topItems) { | ||
state.topItemCount$.next(props.topItems); | ||
} | ||
state.totalCount$.next(props.totalCount); | ||
return { ...state, subscriptions: nextSubscriptions }; | ||
} | ||
render() { | ||
return (React__default.createElement(VirtuosoPresentation, { contextValue: this.state, style: this.props.style, item: this.itemRenderer, footer: this.props.footer, itemHeight: this.props.itemHeight })); | ||
} | ||
} | ||
class GroupedVirtuoso extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.itemRender = (item) => { | ||
if (item.type == 'group') { | ||
return this.props.group(item.groupIndex); | ||
} | ||
else { | ||
return this.props.item(item.transposedIndex, item.groupIndex); | ||
} | ||
}; | ||
this.state = GroupedVirtuoso.getDerivedStateFromProps(this.props, VirtuosoStore(props)); | ||
} | ||
static getDerivedStateFromProps(props, state) { | ||
state.totalCount$.next(props.totalCount); | ||
return null; | ||
state.subscriptions.unsubscribe(); | ||
const nextSubscriptions = new rxjs.Subscription(); | ||
if (props.endReached) { | ||
nextSubscriptions.add(state.endReached$.subscribe(props.endReached)); | ||
} | ||
if (props.scrollingStateChange) { | ||
nextSubscriptions.add(state.isScrolling$.subscribe(props.scrollingStateChange)); | ||
} | ||
state.groupCounts$.next(props.groupCounts); | ||
return { ...state, subscriptions: nextSubscriptions }; | ||
} | ||
render() { | ||
return (React__default.createElement(VirtuosoContext.Provider, { value: this.state }, | ||
React__default.createElement(VirtuosoView, { style: this.props.style || {}, item: this.props.item, footer: this.props.footer, topItemCount: this.props.topItems, fixedItemHeight: this.props.itemHeight !== undefined }))); | ||
return (React__default.createElement(VirtuosoPresentation, { contextValue: this.state, style: this.props.style, item: this.itemRender, footer: this.props.footer, itemHeight: this.props.itemHeight })); | ||
} | ||
} | ||
Virtuoso.defaultProps = { | ||
topItems: 0, | ||
}; | ||
exports.GroupedVirtuoso = GroupedVirtuoso; | ||
exports.Virtuoso = Virtuoso; | ||
exports.VirtuosoPresentation = VirtuosoPresentation; | ||
//# sourceMappingURL=react-virtuoso.cjs.development.js.map |
@@ -1,2 +0,2 @@ | ||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var t=require("react"),i=e(t),r=require("rxjs"),n=require("rxjs/operators"),s=e(require("resize-observer-polyfill"));const l=new class{constructor(){this.level=0}rebalance(){return this}adjust(){return this}remove(){return this}find(){}findMax(){return-Infinity}insert(e,t){return new o({key:e,value:t,level:1})}walkWithin(){return[]}walk(){return[]}ranges(){return[]}rangesWithin(){return[]}empty(){return!0}isSingle(){return!0}isInvariant(){return!0}keys(){return[]}};Object.freeze(l);class o{constructor({key:e,value:t,level:i,left:r=l,right:n=l}){this.key=e,this.value=t,this.level=i,this.left=r,this.right=n}remove(e){const{left:t,right:i}=this;if(e===this.key){if(t.empty())return i;if(i.empty())return t;{const[e,i]=t.last();return this.clone({key:e,value:i,left:t.deleteLast()}).adjust()}}return e<this.key?this.clone({left:t.remove(e)}).adjust():this.clone({right:i.remove(e)}).adjust()}empty(){return!1}find(e){return e===this.key?this.value:e<this.key?this.left.find(e):this.right.find(e)}findMax(e){if(this.key===e)return e;if(this.key<e){const t=this.right.findMax(e);return-Infinity===t?this.key:t}return this.left.findMax(e)}insert(e,t){return e===this.key?this.clone({key:e,value:t}):e<this.key?this.clone({left:this.left.insert(e,t)}).rebalance():this.clone({right:this.right.insert(e,t)}).rebalance()}walkWithin(e,t){const{key:i,value:r}=this;let n=[];return i>e&&(n=n.concat(this.left.walkWithin(e,t))),i>=e&&i<=t&&n.push({key:i,value:r}),i<=t&&(n=n.concat(this.right.walkWithin(e,t))),n}walk(){return[...this.left.walk(),{key:this.key,value:this.value},...this.right.walk()]}last(){return this.right.empty()?[this.key,this.value]:this.right.last()}deleteLast(){return this.right.empty()?this.left:this.clone({right:this.right.deleteLast()}).adjust()}clone(e){return new o({key:void 0!==e.key?e.key:this.key,value:void 0!==e.value?e.value:this.value,level:void 0!==e.level?e.level:this.level,left:void 0!==e.left?e.left:this.left,right:void 0!==e.right?e.right:this.right})}isSingle(){return this.level>this.right.level}rebalance(){return this.skew().split()}adjust(){const{left:e,right:t,level:i}=this;if(t.level>=i-1&&e.level>=i-1)return this;if(i>t.level+1){if(e.isSingle())return this.clone({level:i-1}).skew();if(e.empty()||e.right.empty())throw new Error("Unexpected empty nodes");return e.right.clone({left:e.clone({right:e.right.left}),right:this.clone({left:e.right.right,level:i-1}),level:i})}if(this.isSingle())return this.clone({level:i-1}).split();if(t.empty()||t.left.empty())throw new Error("Unexpected empty nodes");{const e=t.left,r=e.isSingle()?t.level-1:t.level;return e.clone({left:this.clone({right:e.left,level:i-1}),right:t.clone({left:e.right,level:r}).split(),level:e.level+1})}}isInvariant(){const{left:e,right:t,level:i}=this;return i===e.level+1&&((i===t.level||i===t.level+1)&&(!(!t.empty()&&i<=t.right.level)&&(e.isInvariant()&&t.isInvariant())))}keys(){return[...this.left.keys(),this.key,...this.right.keys()]}ranges(){return this.toRanges(this.walk())}rangesWithin(e,t){return this.toRanges(this.walkWithin(e,t))}toRanges(e){if(0===e.length)return[];const t=e[0];let{key:i,value:r}=t;const n=[];for(let t=1;t<=e.length;t++){let s=e[t],l=s?s.key-1:Infinity;n.push({start:i,end:l,value:r}),s&&(i=s.key,r=s.value)}return n}split(){const{right:e,level:t}=this;return e.empty()||e.right.empty()||e.level!=t||e.right.level!=t?this:e.clone({left:this.clone({right:e.left}),level:t+1})}skew(){const{left:e}=this;return e.empty()||e.level!==this.level?this:e.clone({right:this.clone({left:e.right})})}}class a{constructor(e){this.root=e}static empty(){return new a(l)}find(e){return this.root.find(e)}findMax(e){return this.root.findMax(e)}insert(e,t){return new a(this.root.insert(e,t))}remove(e){return new a(this.root.remove(e))}empty(){return this.root.empty()}keys(){return this.root.keys()}walk(){return this.root.walk()}walkWithin(e,t){let i=this.root.findMax(e);return this.root.walkWithin(i,t)}ranges(){return this.root.ranges()}rangesWithin(e,t){let i=this.root.findMax(e);return this.root.rangesWithin(i,t)}isInvariant(){return this.root.isInvariant()}}class h{static create(){return new h(a.empty())}constructor(e){this.rangeTree=e;let t=a.empty(),i=0;for(const{start:r,end:n,value:s}of e.ranges())t=t.insert(i,{startIndex:r,endIndex:n,size:s}),Infinity!==n&&(i+=(n-r+1)*s);this.offsetTree=t}insert(e,t,i){let r=this.rangeTree;if(r.empty())return new h(r.insert(0,i));const n=r.rangesWithin(e-1,t+1);if(n.some(r=>r.start===e&&(r.end===t||Infinity===r.end)&&r.value===i))return this;let s=!1,l=!1;for(const{start:e,end:o,value:a}of n)s?(t>=e||i===a)&&(r=r.remove(e)):(l=a!==i,s=!0),o>t&&t>=e&&a!==i&&(r=r.insert(t+1,a));return l&&(r=r.insert(e,i)),r===this.rangeTree?this:new h(r)}indexRange(e,t){if(0===t)return[];if(this.rangeTree.empty())return[{index:0,size:0,offset:NaN}];const i=this.rangeTree.rangesWithin(e,t),r=[];for(const n of i){const i=Math.max(e,n.start),s=void 0===n.end?Infinity:n.end,l=Math.min(t,s);for(let e=i;e<=l;e++)r.push({index:e,size:n.value,offset:NaN})}return r}range(e,t,i=0,r=Infinity){if(0===r)return[];if(this.offsetTree.empty())return[{index:0,size:0,offset:0}];const n=this.offsetTree.rangesWithin(e,t),s=[];for(let{start:l,value:{startIndex:o,endIndex:a,size:h}}of n){let n=l,u=o;l<e&&(u+=Math.floor((e-l)/h),n+=(u-o)*h),u=Math.max(i,u),a=Math.min(a,r);for(let e=u;e<=a&&!(n>t);e++)s.push({index:e,size:h,offset:n}),n+=h}return s}total(e){const t=this.rangeTree.rangesWithin(0,e);let i=0;for(let{start:r,end:n,value:s}of t)n=Math.min(n,e),i+=(n-r+1)*s;return i}}const u=e=>e.length>0?e[0].offset:0,c=([e,t])=>e.total(t-1),f=({overscan:e=0,totalCount:t,topItems:i=0,itemHeight:s})=>{const l=new r.BehaviorSubject(0),o=new r.BehaviorSubject(0),a=new r.BehaviorSubject(0),f=new r.BehaviorSubject(0),d=new r.Subject,p=new r.BehaviorSubject(t),g=new r.BehaviorSubject(i),m=new r.BehaviorSubject(!1);let v=h.create();s&&(v=v.insert(0,0,s));const y=new r.BehaviorSubject(v);s||d.pipe(n.withLatestFrom(y)).subscribe(([e,t])=>{const i=e.reduce((e,{start:t,end:i,size:r})=>e.insert(t,i,r),t);i!==t&&y.next(i)});const k=r.combineLatest(y,p).pipe(n.map(c)),x=r.combineLatest(k,f).pipe(n.map(([e,t])=>e+t)),w=r.combineLatest(y,g,p).pipe(n.map(([e,t,i])=>{const r=Math.max(0,Math.min(t-1,i));return e.indexRange(0,r)})),b=w.pipe(n.map(e=>e.reduce((e,t)=>e+t.size,0)),n.distinctUntilChanged(),n.auditTime(0)),$=r.combineLatest(l.pipe(n.distinctUntilChanged()),a.pipe(n.distinctUntilChanged()),b.pipe(n.distinctUntilChanged()),o.pipe(n.distinctUntilChanged()),f.pipe(n.distinctUntilChanged()),g,p).pipe(n.withLatestFrom(y),n.scan((e=>(t,[[i,r,n,s,l,o,a],h])=>{const c=u(t),f=c-r+s-l-n,d=Math.max(a-1,0);if(f<i){const t=Math.max(r+n,n),s=r+i+2*e-1;return h.range(t,s,o,d)}if(c>r+n){const t=Math.max(r+n-2*e,n),s=r+i-1;return h.range(t,s,o,d)}return t})(e),[]),n.distinctUntilChanged()),C=$.pipe(n.map(e=>e.length?e[e.length-1].index:0),n.scan((e,t)=>Math.max(e,t)),n.distinctUntilChanged()),I=r.combineLatest($,a,b).pipe(n.map(([e,t,i])=>u(e)-t-i));return a.pipe(n.mapTo(!0),n.skip(1)).subscribe(m),a.pipe(n.debounceTime(200),n.mapTo(!1),n.skip(1)).subscribe(m),{totalCount$:p,footerHeight$:f,itemHeights$:d,listHeight$:o,scrollTop$:a,viewportHeight$:l,list$:$,listOffset$:I,totalHeight$:x,topList$:w,endReached$:C,isScrolling$:m}},d=t.createContext(void 0),p=(e,i=null)=>{const r=t.useRef(null),n=new s(([{contentRect:{height:t}}])=>{e.next(t)});return e=>{e?(n.observe(e),null!==i&&i(e),r.current=e):(n.unobserve(r.current),r.current=null)}};function g(e,i){const[r,s]=t.useState(i);return t.useLayoutEffect(()=>{e.pipe(n.distinctUntilChanged()).subscribe(s)},[]),r}const m={height:"40rem",overflowY:"auto",WebkitOverflowScrolling:"touch",position:"relative",outline:"none"},v=({children:e,style:r})=>{const{scrollTop$:n}=t.useContext(d),s=t.useCallback(e=>{n.next(e.target.scrollTop)},[]),l=t.useCallback(e=>{e&&e.addEventListener("scroll",s,{passive:!0})},[]);return i.createElement("div",{ref:l,style:{...m,...r},tabIndex:0},e)},y=e=>{const t=[];for(const i of e){const e=parseInt(i.dataset.index),r=i.offsetHeight;0===t.length||t[t.length-1].size!==r?t.push({start:e,end:e,size:r}):t[t.length-1].end++}return t},k=({items:e,itemAttributes:t,transform:r,render:n})=>{const s={transform:r,zIndex:""!==r?2:void 0,position:""!==r?"relative":void 0};return e.map(e=>i.createElement("div",Object.assign({key:e.index},t&&t(e),{style:s}),n(e.index)))},x=i.memo(({list$:e,transform:r="",item:n})=>{const{itemHeights$:l}=t.useContext(d),o=g(e,[]),a=t.useRef([]);t.useLayoutEffect(()=>{let e=new s(e=>{l.next(y(e.map(({target:e})=>e)))});return l.next(y(a.current)),a.current.map(t=>{e.observe(t)}),()=>{a.current=[],e.disconnect()}},[o]);const h=t.useCallback(e=>{e&&a.current.push(e)},[o]);return i.createElement(i.Fragment,null,k({items:o,transform:r,render:n,itemAttributes:e=>({"data-index":e.index,"data-known-size":e.size,ref:h})}))}),w=i.memo(({list$:e,transform:t="",item:r})=>{const n=g(e,[]);return i.createElement(i.Fragment,null,k({items:n,transform:t,render:r}))}),b=()=>{const e=g(t.useContext(d).totalHeight$,0);return i.createElement("div",{style:{height:`${e}px`,position:"absolute",top:0}}," ")},$=({footer:e})=>{const r=p(t.useContext(d).footerHeight$);return i.createElement("footer",{ref:r},e())},C={top:0,position:"sticky",height:"100%",overflow:"hidden",WebkitBackfaceVisibility:"hidden"},I=({style:e,footer:r,item:n,topItemCount:s,fixedItemHeight:l})=>{const{listHeight$:o,viewportHeight$:a,listOffset$:h,list$:u,topList$:c}=t.useContext(d),f=g(h,0),m=p(o),y=p(a,e=>{""===e.style.position&&(e.style.position="-webkit-sticky")}),k=`translateY(${f}px)`,I=`translateY(${-f}px)`;return i.createElement(v,{style:e},i.createElement("div",{style:C,ref:y},i.createElement("div",{style:{transform:k}},i.createElement("div",{ref:m},s>0&&i.createElement(x,{list$:c,transform:I,item:n}),i.createElement(l?w:x,{list$:u,item:n}),r&&i.createElement($,{footer:r})))),i.createElement(b,null))};class E extends t.PureComponent{constructor(e){super(e),this.state=f(e),e.endReached&&this.state.endReached$.subscribe(e.endReached),e.scrollingStateChange&&this.state.isScrolling$.subscribe(e.scrollingStateChange)}static getDerivedStateFromProps(e,t){return t.totalCount$.next(e.totalCount),null}render(){return i.createElement(d.Provider,{value:this.state},i.createElement(I,{style:this.props.style||{},item:this.props.item,footer:this.props.footer,topItemCount:this.props.topItems,fixedItemHeight:void 0!==this.props.itemHeight}))}}E.defaultProps={topItems:0},exports.Virtuoso=E; | ||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var t=require("react"),i=e(t),r=require("rxjs"),n=require("rxjs/operators"),s=e(require("resize-observer-polyfill"));const o=t.createContext(void 0);const l=new class{constructor(){this.level=0}rebalance(){return this}adjust(){return this}remove(){return this}find(){}findWith(){}findMax(){return-Infinity}findMaxValue(){}insert(e,t){return new h({key:e,value:t,level:1})}walkWithin(){return[]}walk(){return[]}ranges(){return[]}rangesWithin(){return[]}empty(){return!0}isSingle(){return!0}isInvariant(){return!0}keys(){return[]}};Object.freeze(l);class a extends Error{constructor(e){super(`Unreachable case: ${e}`)}}class h{constructor({key:e,value:t,level:i,left:r=l,right:n=l}){this.key=e,this.value=t,this.level=i,this.left=r,this.right=n}remove(e){const{left:t,right:i}=this;if(e===this.key){if(t.empty())return i;if(i.empty())return t;{const[e,i]=t.last();return this.clone({key:e,value:i,left:t.deleteLast()}).adjust()}}return e<this.key?this.clone({left:t.remove(e)}).adjust():this.clone({right:i.remove(e)}).adjust()}empty(){return!1}find(e){return e===this.key?this.value:e<this.key?this.left.find(e):this.right.find(e)}findWith(e){const t=e(this.value);switch(t){case-1:return this.left.findWith(e);case 0:return[this.key,this.value];case 1:return this.right.findWith(e);default:throw new a(t)}}findMax(e){if(this.key===e)return e;if(this.key<e){const t=this.right.findMax(e);return-Infinity===t?this.key:t}return this.left.findMax(e)}findMaxValue(e){if(this.key===e)return this.value;if(this.key<e){const t=this.right.findMaxValue(e);return void 0===t?this.value:t}return this.left.findMaxValue(e)}insert(e,t){return e===this.key?this.clone({key:e,value:t}):e<this.key?this.clone({left:this.left.insert(e,t)}).rebalance():this.clone({right:this.right.insert(e,t)}).rebalance()}walkWithin(e,t){const{key:i,value:r}=this;let n=[];return i>e&&(n=n.concat(this.left.walkWithin(e,t))),i>=e&&i<=t&&n.push({key:i,value:r}),i<=t&&(n=n.concat(this.right.walkWithin(e,t))),n}walk(){return[...this.left.walk(),{key:this.key,value:this.value},...this.right.walk()]}last(){return this.right.empty()?[this.key,this.value]:this.right.last()}deleteLast(){return this.right.empty()?this.left:this.clone({right:this.right.deleteLast()}).adjust()}clone(e){return new h({key:void 0!==e.key?e.key:this.key,value:void 0!==e.value?e.value:this.value,level:void 0!==e.level?e.level:this.level,left:void 0!==e.left?e.left:this.left,right:void 0!==e.right?e.right:this.right})}isSingle(){return this.level>this.right.level}rebalance(){return this.skew().split()}adjust(){const{left:e,right:t,level:i}=this;if(t.level>=i-1&&e.level>=i-1)return this;if(i>t.level+1){if(e.isSingle())return this.clone({level:i-1}).skew();if(e.empty()||e.right.empty())throw new Error("Unexpected empty nodes");return e.right.clone({left:e.clone({right:e.right.left}),right:this.clone({left:e.right.right,level:i-1}),level:i})}if(this.isSingle())return this.clone({level:i-1}).split();if(t.empty()||t.left.empty())throw new Error("Unexpected empty nodes");{const e=t.left,r=e.isSingle()?t.level-1:t.level;return e.clone({left:this.clone({right:e.left,level:i-1}),right:t.clone({left:e.right,level:r}).split(),level:e.level+1})}}isInvariant(){const{left:e,right:t,level:i}=this;return i===e.level+1&&((i===t.level||i===t.level+1)&&(!(!t.empty()&&i<=t.right.level)&&(e.isInvariant()&&t.isInvariant())))}keys(){return[...this.left.keys(),this.key,...this.right.keys()]}ranges(){return this.toRanges(this.walk())}rangesWithin(e,t){return this.toRanges(this.walkWithin(e,t))}toRanges(e){if(0===e.length)return[];const t=e[0];let{key:i,value:r}=t;const n=[];for(let t=1;t<=e.length;t++){let s=e[t],o=s?s.key-1:Infinity;n.push({start:i,end:o,value:r}),s&&(i=s.key,r=s.value)}return n}split(){const{right:e,level:t}=this;return e.empty()||e.right.empty()||e.level!=t||e.right.level!=t?this:e.clone({left:this.clone({right:e.left}),level:t+1})}skew(){const{left:e}=this;return e.empty()||e.level!==this.level?this:e.clone({right:this.clone({left:e.right})})}}class u{constructor(e){this.root=e}static empty(){return new u(l)}find(e){return this.root.find(e)}findMax(e){return this.root.findMax(e)}findMaxValue(e){if(this.empty())throw new Error("Searching for max value in an empty tree");return this.root.findMaxValue(e)}findWith(e){return this.root.findWith(e)}insert(e,t){return new u(this.root.insert(e,t))}remove(e){return new u(this.root.remove(e))}empty(){return this.root.empty()}keys(){return this.root.keys()}walk(){return this.root.walk()}walkWithin(e,t){let i=this.root.findMax(e);return this.root.walkWithin(i,t)}ranges(){return this.root.ranges()}rangesWithin(e,t){let i=this.root.findMax(e);return this.root.rangesWithin(i,t)}isInvariant(){return this.root.isInvariant()}}class c{static create(){return new c(u.empty())}constructor(e){this.rangeTree=e;let t=u.empty(),i=0;for(const{start:r,end:n,value:s}of e.ranges())t=t.insert(i,{startIndex:r,endIndex:n,size:s}),Infinity!==n&&(i+=(n-r+1)*s);this.offsetTree=t}empty(){return this.rangeTree.empty()}insert(e,t,i){let r=this.rangeTree;if(r.empty())return new c(r.insert(0,i));const n=r.rangesWithin(e-1,t+1);if(n.some(r=>r.start===e&&(r.end===t||Infinity===r.end)&&r.value===i))return this;let s=!1,o=!1;for(const{start:e,end:l,value:a}of n)s?(t>=e||i===a)&&(r=r.remove(e)):(o=a!==i,s=!0),l>t&&t>=e&&(a===i||isNaN(a)||(r=r.insert(t+1,a)));return o&&(r=r.insert(e,i)),r===this.rangeTree?this:new c(r)}insertException(e,t){return this.empty()?new c(this.rangeTree.insert(1,NaN).insert(e,t)):this.insert(e,e,t)}offsetOf(e){if(this.offsetTree.empty())return 0;const t=this.offsetTree.findWith(t=>t.startIndex>e?-1:t.endIndex<e?1:0);if(t){const[i,{startIndex:r,size:n}]=t;return i+(e-r)*n}throw new Error(`Requested offset outside of the known ones, index: ${e}`)}itemAt(e){const t=this.rangeTree.findMaxValue(e);return{index:e,size:t,offset:NaN}}indexRange(e,t){if(0===t)return[];if(this.rangeTree.empty())return[{index:0,size:0,offset:NaN}];const i=this.rangeTree.rangesWithin(e,t),r=[];for(const n of i){const i=Math.max(e,n.start),s=void 0===n.end?Infinity:n.end,o=Math.min(t,s);for(let e=i;e<=o;e++)r.push({index:e,size:n.value,offset:NaN})}return r}range(e,t,i=0,r=Infinity){if(0===r)return[];if(this.offsetTree.empty())return[{index:0,size:0,offset:0}];const n=this.offsetTree.rangesWithin(e,t),s=[];for(let{start:o,value:{startIndex:l,endIndex:a,size:h}}of n){let n=o,u=l;if(o<e&&(u+=Math.floor((e-o)/h),n+=(u-l)*h),u<i&&(n+=(i-u)*h,u=i),isNaN(h))return s.push({index:u,size:0,offset:n}),s;a=Math.min(a,r);for(let e=u;e<=a&&!(n>t);e++)s.push({index:e,size:h,offset:n}),n+=h}return s}total(e){const t=this.rangeTree.rangesWithin(0,e);let i=0;for(let{start:r,end:n,value:s}of t)n=Math.min(n,e),i+=(n-r+1)*s;return i}getOffsets(e){let t=u.empty();return e.forEach(e=>{const i=this.offsetOf(e);t=t.insert(i,e)}),new d(t)}}class d{constructor(e){this.tree=e}findMaxValue(e){return this.tree.findMaxValue(e)}empty(){return this.tree.empty()}}class f{transpose(e){return e.map(e=>({groupIndex:0,index:e.index,offset:e.offset,size:e.size,transposedIndex:e.index,type:"item"}))}}class p{constructor(e){this.count=e.reduce((e,t)=>e+t+1,0);let t=u.empty(),i=0,r=0;for(let n of e)t=t.insert(r,[i,r]),i++,r+=n+1;this.tree=t}totalCount(){return this.count}transpose(e){return e.map(e=>{const t=this.tree.find(e.index);if(t)return{groupIndex:t[0],index:e.index,offset:e.offset,size:e.size,type:"group"};const[i]=this.tree.findMaxValue(e.index);return{groupIndex:i,index:e.index,offset:e.offset,size:e.size,transposedIndex:e.index-i-1,type:"item"}})}groupIndices(){return this.tree.keys()}}const m=e=>e.length>0?e[0].offset:0,g=([e,t])=>e.total(t-1),v=({overscan:e=0,totalCount:t=0,itemHeight:i})=>{const s=new r.BehaviorSubject(0),o=new r.BehaviorSubject(0),l=new r.BehaviorSubject(0),a=new r.BehaviorSubject(0),h=new r.Subject,u=new r.BehaviorSubject(t),d=new r.Subject,v=new r.Subject,y=new r.BehaviorSubject(!1);let x=c.create();const b=new r.BehaviorSubject([]);i&&(x=x.insert(0,0,i));const k=new r.BehaviorSubject(x);i||h.pipe(n.withLatestFrom(k,b)).subscribe(([e,t,i])=>{const r=e.reduce((e,{start:t,end:r,size:n})=>t===r&&i.indexOf(t)>-1?e.insertException(t,n):e.insert(t,r,n),t);r!==t&&k.next(r)});let w=new f;d.subscribe(e=>{w=new p(e),u.next(w.totalCount()),b.next(w.groupIndices())});const I=r.combineLatest(k,u).pipe(n.map(g)),S=r.combineLatest(I,a).pipe(n.map(([e,t])=>e+t)),$=r.combineLatest(k,b).pipe(n.map(([e,t])=>e.getOffsets(t))),C=new r.BehaviorSubject([]);r.combineLatest(k,v,u).pipe(n.filter(e=>e[1]>0),n.map(([e,t,i])=>{const r=Math.max(0,Math.min(t-1,i));return w.transpose(e.indexRange(0,r))})).subscribe(C),r.combineLatest(k,$,l).pipe(n.filter(e=>!e[1].empty()&&!e[0].empty()),n.withLatestFrom(C),n.map(([[e,t,i],r])=>{const n=t.findMaxValue(i);if(1===r.length&&r[0].index===n)return r;const s=e.itemAt(n);return w.transpose([s])}),n.distinctUntilChanged()).subscribe(C);const E=C.pipe(n.map(e=>e.reduce((e,t)=>e+t.size,0)),n.distinctUntilChanged(),n.auditTime(0)),M=C.pipe(n.map(e=>e.length&&e[e.length-1].index+1),n.distinctUntilChanged()),z=r.combineLatest(s.pipe(n.distinctUntilChanged()),l.pipe(n.distinctUntilChanged()),E.pipe(n.distinctUntilChanged()),o.pipe(n.distinctUntilChanged()),a.pipe(n.distinctUntilChanged()),M,u).pipe(n.withLatestFrom(k),n.scan((e=>(t,[[i,r,n,s,o,l,a],h])=>{const u=m(t),c=u-r+s-o-n,d=Math.max(a-1,0),f=t.length>0&&t[0].index<l;if(c<i||f){const t=Math.max(r+n,n),s=r+i+2*e-1;return w.transpose(h.range(t,s,l,d))}if(u>r+n){const t=Math.max(r+n-2*e,n),s=r+i-1;return w.transpose(h.range(t,s,l,d))}return t})(e),[]),n.distinctUntilChanged()),j=z.pipe(n.map(e=>e.length?e[e.length-1].index:0),n.scan((e,t)=>Math.max(e,t)),n.distinctUntilChanged()),H=r.combineLatest(z,l,E).pipe(n.map(([e,t,i])=>m(e)-t-i));l.pipe(n.mapTo(!0),n.skip(1)).subscribe(y),l.pipe(n.debounceTime(200),n.mapTo(!1),n.skip(1)).subscribe(y);const W=new r.Subscription;return{totalCount$:u,footerHeight$:a,itemHeights$:h,listHeight$:o,scrollTop$:l,viewportHeight$:s,topItemCount$:v,groupCounts$:d,list$:z,listOffset$:H,totalHeight$:S,topList$:C,endReached$:j,isScrolling$:y,subscriptions:W,stickyItems$:b}},y=(e,i=null)=>{const r=t.useRef(null),n=new s(([{contentRect:{height:t}}])=>{e.next(t)});return e=>{e?(n.observe(e),null!==i&&i(e),r.current=e):(n.unobserve(r.current),r.current=null)}};function x(e,i){const[r,s]=t.useState(i);return t.useLayoutEffect(()=>{e.pipe(n.distinctUntilChanged()).subscribe(s)},[]),r}const b={height:"40rem",overflowY:"auto",WebkitOverflowScrolling:"touch",position:"relative",outline:"none"},k=({children:e,style:r})=>{const{scrollTop$:n}=t.useContext(o),s=t.useCallback(e=>{n.next(e.target.scrollTop)},[]),l=t.useCallback(e=>{e&&e.addEventListener("scroll",s,{passive:!0})},[]);return i.createElement("div",{ref:l,style:{...b,...r},tabIndex:0},e)};class w{constructor(e){this.itemElements=[],this.trackRef=(e=>{e&&this.itemElements.push(e)}),this.itemHeights$=e}publishSizes(e){const t=[];for(const i of e){const e=parseInt(i.dataset.index),r=i.offsetHeight;0===t.length||t[t.length-1].size!==r?t.push({start:e,end:e,size:r}):t[t.length-1].end++}this.itemHeights$.next(t)}init(){this.observer=new s(e=>{this.publishSizes(e.map(({target:e})=>e))}),this.publishSizes(this.itemElements),this.itemElements.map(e=>{this.observer.observe(e)})}destroy(){this.itemElements=[],this.observer.disconnect()}getItemAttributes(){return e=>({"data-index":e.index,"data-known-size":e.size,ref:this.trackRef})}}const I=({items:e,itemAttributes:t,render:r,getStyle:n})=>e.map(e=>i.createElement("div",Object.assign({key:e.index},t&&t(e),{style:n(e.index)}),r(e))),S=i.memo(({items:e,render:r,getStyle:n})=>{const{itemHeights$:s}=t.useContext(o),l=t.useRef(new w(s));return t.useLayoutEffect(()=>(l.current.init(),()=>{l.current.destroy()}),[e]),i.createElement(i.Fragment,null,I({items:e,render:r,itemAttributes:l.current.getItemAttributes(),getStyle:n}))}),$=i.memo(({items:e,render:t,getStyle:r})=>i.createElement(i.Fragment,null,I({items:e,render:t,getStyle:r}))),C=i.memo(({list$:e,transform:r="",render:n,fixedItemHeight:s})=>{const{stickyItems$:l}=t.useContext(o),a=x(e,[]),h=x(l,[]),u=t.useCallback(e=>{const t=h.some(t=>t===e),i={transform:r,zIndex:t?2:void 0,position:t?"relative":void 0};return i},[h,r]);return 0===a.length?null:i.createElement(s?$:S,Object.assign({},{items:a,render:n,getStyle:u}))}),E=()=>{const e=x(t.useContext(o).totalHeight$,0);return i.createElement("div",{style:{height:`${e}px`,position:"absolute",top:0}}," ")},M=({footer:e})=>{const r=y(t.useContext(o).footerHeight$);return i.createElement("footer",{ref:r},e())},z={top:0,position:"sticky",height:"100%",overflow:"hidden",WebkitBackfaceVisibility:"hidden"},j=({style:e,footer:r,item:n,fixedItemHeight:s})=>{const{listHeight$:l,viewportHeight$:a,listOffset$:h,list$:u,topList$:c}=t.useContext(o),d=x(h,0),f=y(l),p=y(a,e=>{""===e.style.position&&(e.style.position="-webkit-sticky")}),m=`translateY(${d}px)`,g=`translateY(${-d}px)`;return i.createElement(k,{style:e},i.createElement("div",{style:z,ref:p},i.createElement("div",{style:{transform:m}},i.createElement("div",{ref:f},i.createElement(C,{list$:c,transform:g,render:n,fixedItemHeight:s}),i.createElement(C,{list$:u,render:n,fixedItemHeight:s}),r&&i.createElement(M,{footer:r})))),i.createElement(E,null))},H=({contextValue:e,style:t,item:r,footer:n,itemHeight:s})=>i.createElement(o.Provider,{value:e},i.createElement(j,{style:t||{},item:r,footer:n,fixedItemHeight:void 0!==s}));class W extends t.PureComponent{constructor(e){super(e),this.itemRenderer=(e=>this.props.item(e.index)),this.state=W.getDerivedStateFromProps(this.props,v(e))}static getDerivedStateFromProps(e,t){t.subscriptions.unsubscribe();const i=new r.Subscription;return e.endReached&&i.add(t.endReached$.subscribe(e.endReached)),e.scrollingStateChange&&i.add(t.isScrolling$.subscribe(e.scrollingStateChange)),e.topItems&&t.topItemCount$.next(e.topItems),t.totalCount$.next(e.totalCount),{...t,subscriptions:i}}render(){return i.createElement(H,{contextValue:this.state,style:this.props.style,item:this.itemRenderer,footer:this.props.footer,itemHeight:this.props.itemHeight})}}class R extends t.PureComponent{constructor(e){super(e),this.itemRender=(e=>"group"==e.type?this.props.group(e.groupIndex):this.props.item(e.transposedIndex,e.groupIndex)),this.state=R.getDerivedStateFromProps(this.props,v(e))}static getDerivedStateFromProps(e,t){t.subscriptions.unsubscribe();const i=new r.Subscription;return e.endReached&&i.add(t.endReached$.subscribe(e.endReached)),e.scrollingStateChange&&i.add(t.isScrolling$.subscribe(e.scrollingStateChange)),t.groupCounts$.next(e.groupCounts),{...t,subscriptions:i}}render(){return i.createElement(H,{contextValue:this.state,style:this.props.style,item:this.itemRender,footer:this.props.footer,itemHeight:this.props.itemHeight})}}exports.GroupedVirtuoso=R,exports.Virtuoso=W,exports.VirtuosoPresentation=H; | ||
//# sourceMappingURL=react-virtuoso.cjs.production.js.map |
@@ -1,2 +0,2 @@ | ||
import e,{createContext as t,useState as r,useLayoutEffect as i,useRef as n,useContext as s,useCallback as l,PureComponent as o}from"react";import{BehaviorSubject as a,Subject as h,combineLatest as u}from"rxjs";import{withLatestFrom as c,map as f,distinctUntilChanged as p,auditTime as g,scan as m,mapTo as d,skip as v,debounceTime as y}from"rxjs/operators";import k from"resize-observer-polyfill";const x=new class{constructor(){this.level=0}rebalance(){return this}adjust(){return this}remove(){return this}find(){}findMax(){return-Infinity}insert(e,t){return new w({key:e,value:t,level:1})}walkWithin(){return[]}walk(){return[]}ranges(){return[]}rangesWithin(){return[]}empty(){return!0}isSingle(){return!0}isInvariant(){return!0}keys(){return[]}};Object.freeze(x);class w{constructor({key:e,value:t,level:r,left:i=x,right:n=x}){this.key=e,this.value=t,this.level=r,this.left=i,this.right=n}remove(e){const{left:t,right:r}=this;if(e===this.key){if(t.empty())return r;if(r.empty())return t;{const[e,r]=t.last();return this.clone({key:e,value:r,left:t.deleteLast()}).adjust()}}return e<this.key?this.clone({left:t.remove(e)}).adjust():this.clone({right:r.remove(e)}).adjust()}empty(){return!1}find(e){return e===this.key?this.value:e<this.key?this.left.find(e):this.right.find(e)}findMax(e){if(this.key===e)return e;if(this.key<e){const t=this.right.findMax(e);return-Infinity===t?this.key:t}return this.left.findMax(e)}insert(e,t){return e===this.key?this.clone({key:e,value:t}):e<this.key?this.clone({left:this.left.insert(e,t)}).rebalance():this.clone({right:this.right.insert(e,t)}).rebalance()}walkWithin(e,t){const{key:r,value:i}=this;let n=[];return r>e&&(n=n.concat(this.left.walkWithin(e,t))),r>=e&&r<=t&&n.push({key:r,value:i}),r<=t&&(n=n.concat(this.right.walkWithin(e,t))),n}walk(){return[...this.left.walk(),{key:this.key,value:this.value},...this.right.walk()]}last(){return this.right.empty()?[this.key,this.value]:this.right.last()}deleteLast(){return this.right.empty()?this.left:this.clone({right:this.right.deleteLast()}).adjust()}clone(e){return new w({key:void 0!==e.key?e.key:this.key,value:void 0!==e.value?e.value:this.value,level:void 0!==e.level?e.level:this.level,left:void 0!==e.left?e.left:this.left,right:void 0!==e.right?e.right:this.right})}isSingle(){return this.level>this.right.level}rebalance(){return this.skew().split()}adjust(){const{left:e,right:t,level:r}=this;if(t.level>=r-1&&e.level>=r-1)return this;if(r>t.level+1){if(e.isSingle())return this.clone({level:r-1}).skew();if(e.empty()||e.right.empty())throw new Error("Unexpected empty nodes");return e.right.clone({left:e.clone({right:e.right.left}),right:this.clone({left:e.right.right,level:r-1}),level:r})}if(this.isSingle())return this.clone({level:r-1}).split();if(t.empty()||t.left.empty())throw new Error("Unexpected empty nodes");{const e=t.left,i=e.isSingle()?t.level-1:t.level;return e.clone({left:this.clone({right:e.left,level:r-1}),right:t.clone({left:e.right,level:i}).split(),level:e.level+1})}}isInvariant(){const{left:e,right:t,level:r}=this;return r===e.level+1&&((r===t.level||r===t.level+1)&&(!(!t.empty()&&r<=t.right.level)&&(e.isInvariant()&&t.isInvariant())))}keys(){return[...this.left.keys(),this.key,...this.right.keys()]}ranges(){return this.toRanges(this.walk())}rangesWithin(e,t){return this.toRanges(this.walkWithin(e,t))}toRanges(e){if(0===e.length)return[];const t=e[0];let{key:r,value:i}=t;const n=[];for(let t=1;t<=e.length;t++){let s=e[t],l=s?s.key-1:Infinity;n.push({start:r,end:l,value:i}),s&&(r=s.key,i=s.value)}return n}split(){const{right:e,level:t}=this;return e.empty()||e.right.empty()||e.level!=t||e.right.level!=t?this:e.clone({left:this.clone({right:e.left}),level:t+1})}skew(){const{left:e}=this;return e.empty()||e.level!==this.level?this:e.clone({right:this.clone({left:e.right})})}}class ${constructor(e){this.root=e}static empty(){return new $(x)}find(e){return this.root.find(e)}findMax(e){return this.root.findMax(e)}insert(e,t){return new $(this.root.insert(e,t))}remove(e){return new $(this.root.remove(e))}empty(){return this.root.empty()}keys(){return this.root.keys()}walk(){return this.root.walk()}walkWithin(e,t){let r=this.root.findMax(e);return this.root.walkWithin(r,t)}ranges(){return this.root.ranges()}rangesWithin(e,t){let r=this.root.findMax(e);return this.root.rangesWithin(r,t)}isInvariant(){return this.root.isInvariant()}}class b{static create(){return new b($.empty())}constructor(e){this.rangeTree=e;let t=$.empty(),r=0;for(const{start:i,end:n,value:s}of e.ranges())t=t.insert(r,{startIndex:i,endIndex:n,size:s}),Infinity!==n&&(r+=(n-i+1)*s);this.offsetTree=t}insert(e,t,r){let i=this.rangeTree;if(i.empty())return new b(i.insert(0,r));const n=i.rangesWithin(e-1,t+1);if(n.some(i=>i.start===e&&(i.end===t||Infinity===i.end)&&i.value===r))return this;let s=!1,l=!1;for(const{start:e,end:o,value:a}of n)s?(t>=e||r===a)&&(i=i.remove(e)):(l=a!==r,s=!0),o>t&&t>=e&&a!==r&&(i=i.insert(t+1,a));return l&&(i=i.insert(e,r)),i===this.rangeTree?this:new b(i)}indexRange(e,t){if(0===t)return[];if(this.rangeTree.empty())return[{index:0,size:0,offset:NaN}];const r=this.rangeTree.rangesWithin(e,t),i=[];for(const n of r){const r=Math.max(e,n.start),s=void 0===n.end?Infinity:n.end,l=Math.min(t,s);for(let e=r;e<=l;e++)i.push({index:e,size:n.value,offset:NaN})}return i}range(e,t,r=0,i=Infinity){if(0===i)return[];if(this.offsetTree.empty())return[{index:0,size:0,offset:0}];const n=this.offsetTree.rangesWithin(e,t),s=[];for(let{start:l,value:{startIndex:o,endIndex:a,size:h}}of n){let n=l,u=o;l<e&&(u+=Math.floor((e-l)/h),n+=(u-o)*h),u=Math.max(r,u),a=Math.min(a,i);for(let e=u;e<=a&&!(n>t);e++)s.push({index:e,size:h,offset:n}),n+=h}return s}total(e){const t=this.rangeTree.rangesWithin(0,e);let r=0;for(let{start:i,end:n,value:s}of t)n=Math.min(n,e),r+=(n-i+1)*s;return r}}const I=e=>e.length>0?e[0].offset:0,M=([e,t])=>e.total(t-1),E=({overscan:e=0,totalCount:t,topItems:r=0,itemHeight:i})=>{const n=new a(0),s=new a(0),l=new a(0),o=new a(0),k=new h,x=new a(t),w=new a(r),$=new a(!1);let E=b.create();i&&(E=E.insert(0,0,i));const W=new a(E);i||k.pipe(c(W)).subscribe(([e,t])=>{const r=e.reduce((e,{start:t,end:r,size:i})=>e.insert(t,r,i),t);r!==t&&W.next(r)});const z=u(W,x).pipe(f(M)),H=u(z,o).pipe(f(([e,t])=>e+t)),T=u(W,w,x).pipe(f(([e,t,r])=>{const i=Math.max(0,Math.min(t-1,r));return e.indexRange(0,i)})),S=T.pipe(f(e=>e.reduce((e,t)=>e+t.size,0)),p(),g(0)),j=u(n.pipe(p()),l.pipe(p()),S.pipe(p()),s.pipe(p()),o.pipe(p()),w,x).pipe(c(W),m((e=>(t,[[r,i,n,s,l,o,a],h])=>{const u=I(t),c=u-i+s-l-n,f=Math.max(a-1,0);if(c<r){const t=Math.max(i+n,n),s=i+r+2*e-1;return h.range(t,s,o,f)}if(u>i+n){const t=Math.max(i+n-2*e,n),s=i+r-1;return h.range(t,s,o,f)}return t})(e),[]),p()),R=j.pipe(f(e=>e.length?e[e.length-1].index:0),m((e,t)=>Math.max(e,t)),p()),C=u(j,l,S).pipe(f(([e,t,r])=>I(e)-t-r));return l.pipe(d(!0),v(1)).subscribe($),l.pipe(y(200),d(!1),v(1)).subscribe($),{totalCount$:x,footerHeight$:o,itemHeights$:k,listHeight$:s,scrollTop$:l,viewportHeight$:n,list$:j,listOffset$:C,totalHeight$:H,topList$:T,endReached$:R,isScrolling$:$}},W=t(void 0),z=(e,t=null)=>{const r=n(null),i=new k(([{contentRect:{height:t}}])=>{e.next(t)});return e=>{e?(i.observe(e),null!==t&&t(e),r.current=e):(i.unobserve(r.current),r.current=null)}};function H(e,t){const[n,s]=r(t);return i(()=>{e.pipe(p()).subscribe(s)},[]),n}const T={height:"40rem",overflowY:"auto",WebkitOverflowScrolling:"touch",position:"relative",outline:"none"},S=({children:t,style:r})=>{const{scrollTop$:i}=s(W),n=l(e=>{i.next(e.target.scrollTop)},[]),o=l(e=>{e&&e.addEventListener("scroll",n,{passive:!0})},[]);return e.createElement("div",{ref:o,style:{...T,...r},tabIndex:0},t)},j=e=>{const t=[];for(const r of e){const e=parseInt(r.dataset.index),i=r.offsetHeight;0===t.length||t[t.length-1].size!==i?t.push({start:e,end:e,size:i}):t[t.length-1].end++}return t},R=({items:t,itemAttributes:r,transform:i,render:n})=>{const s={transform:i,zIndex:""!==i?2:void 0,position:""!==i?"relative":void 0};return t.map(t=>e.createElement("div",Object.assign({key:t.index},r&&r(t),{style:s}),n(t.index)))},C=e.memo(({list$:t,transform:r="",item:o})=>{const{itemHeights$:a}=s(W),h=H(t,[]),u=n([]);i(()=>{let e=new k(e=>{a.next(j(e.map(({target:e})=>e)))});return a.next(j(u.current)),u.current.map(t=>{e.observe(t)}),()=>{u.current=[],e.disconnect()}},[h]);const c=l(e=>{e&&u.current.push(e)},[h]);return e.createElement(e.Fragment,null,R({items:h,transform:r,render:o,itemAttributes:e=>({"data-index":e.index,"data-known-size":e.size,ref:c})}))}),L=e.memo(({list$:t,transform:r="",item:i})=>{const n=H(t,[]);return e.createElement(e.Fragment,null,R({items:n,transform:r,render:i}))}),O=()=>{const t=H(s(W).totalHeight$,0);return e.createElement("div",{style:{height:`${t}px`,position:"absolute",top:0}}," ")},N=({footer:t})=>{const r=z(s(W).footerHeight$);return e.createElement("footer",{ref:r},t())},F={top:0,position:"sticky",height:"100%",overflow:"hidden",WebkitBackfaceVisibility:"hidden"},P=({style:t,footer:r,item:i,topItemCount:n,fixedItemHeight:l})=>{const{listHeight$:o,viewportHeight$:a,listOffset$:h,list$:u,topList$:c}=s(W),f=H(h,0),p=z(o),g=z(a,e=>{""===e.style.position&&(e.style.position="-webkit-sticky")}),m=`translateY(${f}px)`,d=`translateY(${-f}px)`;return e.createElement(S,{style:t},e.createElement("div",{style:F,ref:g},e.createElement("div",{style:{transform:m}},e.createElement("div",{ref:p},n>0&&e.createElement(C,{list$:c,transform:d,item:i}),e.createElement(l?L:C,{list$:u,item:i}),r&&e.createElement(N,{footer:r})))),e.createElement(O,null))};class Y extends o{constructor(e){super(e),this.state=E(e),e.endReached&&this.state.endReached$.subscribe(e.endReached),e.scrollingStateChange&&this.state.isScrolling$.subscribe(e.scrollingStateChange)}static getDerivedStateFromProps(e,t){return t.totalCount$.next(e.totalCount),null}render(){return e.createElement(W.Provider,{value:this.state},e.createElement(P,{style:this.props.style||{},item:this.props.item,footer:this.props.footer,topItemCount:this.props.topItems,fixedItemHeight:void 0!==this.props.itemHeight}))}}Y.defaultProps={topItems:0};export{Y as Virtuoso}; | ||
import e,{createContext as t,useState as r,useLayoutEffect as i,useRef as n,useContext as s,useCallback as o,PureComponent as l}from"react";import{BehaviorSubject as h,Subject as a,combineLatest as u,Subscription as c}from"rxjs";import{withLatestFrom as f,map as d,filter as p,distinctUntilChanged as g,auditTime as m,scan as v,mapTo as y,skip as x,debounceTime as k}from"rxjs/operators";import w from"resize-observer-polyfill";const b=t(void 0);const I=new class{constructor(){this.level=0}rebalance(){return this}adjust(){return this}remove(){return this}find(){}findWith(){}findMax(){return-Infinity}findMaxValue(){}insert(e,t){return new E({key:e,value:t,level:1})}walkWithin(){return[]}walk(){return[]}ranges(){return[]}rangesWithin(){return[]}empty(){return!0}isSingle(){return!0}isInvariant(){return!0}keys(){return[]}};Object.freeze(I);class $ extends Error{constructor(e){super(`Unreachable case: ${e}`)}}class E{constructor({key:e,value:t,level:r,left:i=I,right:n=I}){this.key=e,this.value=t,this.level=r,this.left=i,this.right=n}remove(e){const{left:t,right:r}=this;if(e===this.key){if(t.empty())return r;if(r.empty())return t;{const[e,r]=t.last();return this.clone({key:e,value:r,left:t.deleteLast()}).adjust()}}return e<this.key?this.clone({left:t.remove(e)}).adjust():this.clone({right:r.remove(e)}).adjust()}empty(){return!1}find(e){return e===this.key?this.value:e<this.key?this.left.find(e):this.right.find(e)}findWith(e){const t=e(this.value);switch(t){case-1:return this.left.findWith(e);case 0:return[this.key,this.value];case 1:return this.right.findWith(e);default:throw new $(t)}}findMax(e){if(this.key===e)return e;if(this.key<e){const t=this.right.findMax(e);return-Infinity===t?this.key:t}return this.left.findMax(e)}findMaxValue(e){if(this.key===e)return this.value;if(this.key<e){const t=this.right.findMaxValue(e);return void 0===t?this.value:t}return this.left.findMaxValue(e)}insert(e,t){return e===this.key?this.clone({key:e,value:t}):e<this.key?this.clone({left:this.left.insert(e,t)}).rebalance():this.clone({right:this.right.insert(e,t)}).rebalance()}walkWithin(e,t){const{key:r,value:i}=this;let n=[];return r>e&&(n=n.concat(this.left.walkWithin(e,t))),r>=e&&r<=t&&n.push({key:r,value:i}),r<=t&&(n=n.concat(this.right.walkWithin(e,t))),n}walk(){return[...this.left.walk(),{key:this.key,value:this.value},...this.right.walk()]}last(){return this.right.empty()?[this.key,this.value]:this.right.last()}deleteLast(){return this.right.empty()?this.left:this.clone({right:this.right.deleteLast()}).adjust()}clone(e){return new E({key:void 0!==e.key?e.key:this.key,value:void 0!==e.value?e.value:this.value,level:void 0!==e.level?e.level:this.level,left:void 0!==e.left?e.left:this.left,right:void 0!==e.right?e.right:this.right})}isSingle(){return this.level>this.right.level}rebalance(){return this.skew().split()}adjust(){const{left:e,right:t,level:r}=this;if(t.level>=r-1&&e.level>=r-1)return this;if(r>t.level+1){if(e.isSingle())return this.clone({level:r-1}).skew();if(e.empty()||e.right.empty())throw new Error("Unexpected empty nodes");return e.right.clone({left:e.clone({right:e.right.left}),right:this.clone({left:e.right.right,level:r-1}),level:r})}if(this.isSingle())return this.clone({level:r-1}).split();if(t.empty()||t.left.empty())throw new Error("Unexpected empty nodes");{const e=t.left,i=e.isSingle()?t.level-1:t.level;return e.clone({left:this.clone({right:e.left,level:r-1}),right:t.clone({left:e.right,level:i}).split(),level:e.level+1})}}isInvariant(){const{left:e,right:t,level:r}=this;return r===e.level+1&&((r===t.level||r===t.level+1)&&(!(!t.empty()&&r<=t.right.level)&&(e.isInvariant()&&t.isInvariant())))}keys(){return[...this.left.keys(),this.key,...this.right.keys()]}ranges(){return this.toRanges(this.walk())}rangesWithin(e,t){return this.toRanges(this.walkWithin(e,t))}toRanges(e){if(0===e.length)return[];const t=e[0];let{key:r,value:i}=t;const n=[];for(let t=1;t<=e.length;t++){let s=e[t],o=s?s.key-1:Infinity;n.push({start:r,end:o,value:i}),s&&(r=s.key,i=s.value)}return n}split(){const{right:e,level:t}=this;return e.empty()||e.right.empty()||e.level!=t||e.right.level!=t?this:e.clone({left:this.clone({right:e.left}),level:t+1})}skew(){const{left:e}=this;return e.empty()||e.level!==this.level?this:e.clone({right:this.clone({left:e.right})})}}class M{constructor(e){this.root=e}static empty(){return new M(I)}find(e){return this.root.find(e)}findMax(e){return this.root.findMax(e)}findMaxValue(e){if(this.empty())throw new Error("Searching for max value in an empty tree");return this.root.findMaxValue(e)}findWith(e){return this.root.findWith(e)}insert(e,t){return new M(this.root.insert(e,t))}remove(e){return new M(this.root.remove(e))}empty(){return this.root.empty()}keys(){return this.root.keys()}walk(){return this.root.walk()}walkWithin(e,t){let r=this.root.findMax(e);return this.root.walkWithin(r,t)}ranges(){return this.root.ranges()}rangesWithin(e,t){let r=this.root.findMax(e);return this.root.rangesWithin(r,t)}isInvariant(){return this.root.isInvariant()}}class z{static create(){return new z(M.empty())}constructor(e){this.rangeTree=e;let t=M.empty(),r=0;for(const{start:i,end:n,value:s}of e.ranges())t=t.insert(r,{startIndex:i,endIndex:n,size:s}),Infinity!==n&&(r+=(n-i+1)*s);this.offsetTree=t}empty(){return this.rangeTree.empty()}insert(e,t,r){let i=this.rangeTree;if(i.empty())return new z(i.insert(0,r));const n=i.rangesWithin(e-1,t+1);if(n.some(i=>i.start===e&&(i.end===t||Infinity===i.end)&&i.value===r))return this;let s=!1,o=!1;for(const{start:e,end:l,value:h}of n)s?(t>=e||r===h)&&(i=i.remove(e)):(o=h!==r,s=!0),l>t&&t>=e&&(h===r||isNaN(h)||(i=i.insert(t+1,h)));return o&&(i=i.insert(e,r)),i===this.rangeTree?this:new z(i)}insertException(e,t){return this.empty()?new z(this.rangeTree.insert(1,NaN).insert(e,t)):this.insert(e,e,t)}offsetOf(e){if(this.offsetTree.empty())return 0;const t=this.offsetTree.findWith(t=>t.startIndex>e?-1:t.endIndex<e?1:0);if(t){const[r,{startIndex:i,size:n}]=t;return r+(e-i)*n}throw new Error(`Requested offset outside of the known ones, index: ${e}`)}itemAt(e){const t=this.rangeTree.findMaxValue(e);return{index:e,size:t,offset:NaN}}indexRange(e,t){if(0===t)return[];if(this.rangeTree.empty())return[{index:0,size:0,offset:NaN}];const r=this.rangeTree.rangesWithin(e,t),i=[];for(const n of r){const r=Math.max(e,n.start),s=void 0===n.end?Infinity:n.end,o=Math.min(t,s);for(let e=r;e<=o;e++)i.push({index:e,size:n.value,offset:NaN})}return i}range(e,t,r=0,i=Infinity){if(0===i)return[];if(this.offsetTree.empty())return[{index:0,size:0,offset:0}];const n=this.offsetTree.rangesWithin(e,t),s=[];for(let{start:o,value:{startIndex:l,endIndex:h,size:a}}of n){let n=o,u=l;if(o<e&&(u+=Math.floor((e-o)/a),n+=(u-l)*a),u<r&&(n+=(r-u)*a,u=r),isNaN(a))return s.push({index:u,size:0,offset:n}),s;h=Math.min(h,i);for(let e=u;e<=h&&!(n>t);e++)s.push({index:e,size:a,offset:n}),n+=a}return s}total(e){const t=this.rangeTree.rangesWithin(0,e);let r=0;for(let{start:i,end:n,value:s}of t)n=Math.min(n,e),r+=(n-i+1)*s;return r}getOffsets(e){let t=M.empty();return e.forEach(e=>{const r=this.offsetOf(e);t=t.insert(r,e)}),new S(t)}}class S{constructor(e){this.tree=e}findMaxValue(e){return this.tree.findMaxValue(e)}empty(){return this.tree.empty()}}class H{transpose(e){return e.map(e=>({groupIndex:0,index:e.index,offset:e.offset,size:e.size,transposedIndex:e.index,type:"item"}))}}class W{constructor(e){this.count=e.reduce((e,t)=>e+t+1,0);let t=M.empty(),r=0,i=0;for(let n of e)t=t.insert(i,[r,i]),r++,i+=n+1;this.tree=t}totalCount(){return this.count}transpose(e){return e.map(e=>{const t=this.tree.find(e.index);if(t)return{groupIndex:t[0],index:e.index,offset:e.offset,size:e.size,type:"group"};const[r]=this.tree.findMaxValue(e.index);return{groupIndex:r,index:e.index,offset:e.offset,size:e.size,transposedIndex:e.index-r-1,type:"item"}})}groupIndices(){return this.tree.keys()}}const R=e=>e.length>0?e[0].offset:0,T=([e,t])=>e.total(t-1),C=({overscan:e=0,totalCount:t=0,itemHeight:r})=>{const i=new h(0),n=new h(0),s=new h(0),o=new h(0),l=new a,w=new h(t),b=new a,I=new a,$=new h(!1);let E=z.create();const M=new h([]);r&&(E=E.insert(0,0,r));const S=new h(E);r||l.pipe(f(S,M)).subscribe(([e,t,r])=>{const i=e.reduce((e,{start:t,end:i,size:n})=>t===i&&r.indexOf(t)>-1?e.insertException(t,n):e.insert(t,i,n),t);i!==t&&S.next(i)});let C=new H;b.subscribe(e=>{C=new W(e),w.next(C.totalCount()),M.next(C.groupIndices())});const V=u(S,w).pipe(d(T)),N=u(V,o).pipe(d(([e,t])=>e+t)),j=u(S,M).pipe(d(([e,t])=>e.getOffsets(t))),O=new h([]);u(S,I,w).pipe(p(e=>e[1]>0),d(([e,t,r])=>{const i=Math.max(0,Math.min(t-1,r));return C.transpose(e.indexRange(0,i))})).subscribe(O),u(S,j,s).pipe(p(e=>!e[1].empty()&&!e[0].empty()),f(O),d(([[e,t,r],i])=>{const n=t.findMaxValue(r);if(1===i.length&&i[0].index===n)return i;const s=e.itemAt(n);return C.transpose([s])}),g()).subscribe(O);const A=O.pipe(d(e=>e.reduce((e,t)=>e+t.size,0)),g(),m(0)),F=O.pipe(d(e=>e.length&&e[e.length-1].index+1),g()),L=u(i.pipe(g()),s.pipe(g()),A.pipe(g()),n.pipe(g()),o.pipe(g()),F,w).pipe(f(S),v((e=>(t,[[r,i,n,s,o,l,h],a])=>{const u=R(t),c=u-i+s-o-n,f=Math.max(h-1,0),d=t.length>0&&t[0].index<l;if(c<r||d){const t=Math.max(i+n,n),s=i+r+2*e-1;return C.transpose(a.range(t,s,l,f))}if(u>i+n){const t=Math.max(i+n-2*e,n),s=i+r-1;return C.transpose(a.range(t,s,l,f))}return t})(e),[]),g()),P=L.pipe(d(e=>e.length?e[e.length-1].index:0),v((e,t)=>Math.max(e,t)),g()),D=u(L,s,A).pipe(d(([e,t,r])=>R(e)-t-r));s.pipe(y(!0),x(1)).subscribe($),s.pipe(k(200),y(!1),x(1)).subscribe($);const U=new c;return{totalCount$:w,footerHeight$:o,itemHeights$:l,listHeight$:n,scrollTop$:s,viewportHeight$:i,topItemCount$:I,groupCounts$:b,list$:L,listOffset$:D,totalHeight$:N,topList$:O,endReached$:P,isScrolling$:$,subscriptions:U,stickyItems$:M}},V=(e,t=null)=>{const r=n(null),i=new w(([{contentRect:{height:t}}])=>{e.next(t)});return e=>{e?(i.observe(e),null!==t&&t(e),r.current=e):(i.unobserve(r.current),r.current=null)}};function N(e,t){const[n,s]=r(t);return i(()=>{e.pipe(g()).subscribe(s)},[]),n}const j={height:"40rem",overflowY:"auto",WebkitOverflowScrolling:"touch",position:"relative",outline:"none"},O=({children:t,style:r})=>{const{scrollTop$:i}=s(b),n=o(e=>{i.next(e.target.scrollTop)},[]),l=o(e=>{e&&e.addEventListener("scroll",n,{passive:!0})},[]);return e.createElement("div",{ref:l,style:{...j,...r},tabIndex:0},t)};class A{constructor(e){this.itemElements=[],this.trackRef=(e=>{e&&this.itemElements.push(e)}),this.itemHeights$=e}publishSizes(e){const t=[];for(const r of e){const e=parseInt(r.dataset.index),i=r.offsetHeight;0===t.length||t[t.length-1].size!==i?t.push({start:e,end:e,size:i}):t[t.length-1].end++}this.itemHeights$.next(t)}init(){this.observer=new w(e=>{this.publishSizes(e.map(({target:e})=>e))}),this.publishSizes(this.itemElements),this.itemElements.map(e=>{this.observer.observe(e)})}destroy(){this.itemElements=[],this.observer.disconnect()}getItemAttributes(){return e=>({"data-index":e.index,"data-known-size":e.size,ref:this.trackRef})}}const F=({items:t,itemAttributes:r,render:i,getStyle:n})=>t.map(t=>e.createElement("div",Object.assign({key:t.index},r&&r(t),{style:n(t.index)}),i(t))),L=e.memo(({items:t,render:r,getStyle:o})=>{const{itemHeights$:l}=s(b),h=n(new A(l));return i(()=>(h.current.init(),()=>{h.current.destroy()}),[t]),e.createElement(e.Fragment,null,F({items:t,render:r,itemAttributes:h.current.getItemAttributes(),getStyle:o}))}),P=e.memo(({items:t,render:r,getStyle:i})=>e.createElement(e.Fragment,null,F({items:t,render:r,getStyle:i}))),D=e.memo(({list$:t,transform:r="",render:i,fixedItemHeight:n})=>{const{stickyItems$:l}=s(b),h=N(t,[]),a=N(l,[]),u=o(e=>{const t=a.some(t=>t===e),i={transform:r,zIndex:t?2:void 0,position:t?"relative":void 0};return i},[a,r]);return 0===h.length?null:e.createElement(n?P:L,Object.assign({},{items:h,render:i,getStyle:u}))}),U=()=>{const t=N(s(b).totalHeight$,0);return e.createElement("div",{style:{height:`${t}px`,position:"absolute",top:0}}," ")},Y=({footer:t})=>{const r=V(s(b).footerHeight$);return e.createElement("footer",{ref:r},t())},q={top:0,position:"sticky",height:"100%",overflow:"hidden",WebkitBackfaceVisibility:"hidden"},B=({style:t,footer:r,item:i,fixedItemHeight:n})=>{const{listHeight$:o,viewportHeight$:l,listOffset$:h,list$:a,topList$:u}=s(b),c=N(h,0),f=V(o),d=V(l,e=>{""===e.style.position&&(e.style.position="-webkit-sticky")}),p=`translateY(${c}px)`,g=`translateY(${-c}px)`;return e.createElement(O,{style:t},e.createElement("div",{style:q,ref:d},e.createElement("div",{style:{transform:p}},e.createElement("div",{ref:f},e.createElement(D,{list$:u,transform:g,render:i,fixedItemHeight:n}),e.createElement(D,{list$:a,render:i,fixedItemHeight:n}),r&&e.createElement(Y,{footer:r})))),e.createElement(U,null))},G=({contextValue:t,style:r,item:i,footer:n,itemHeight:s})=>e.createElement(b.Provider,{value:t},e.createElement(B,{style:r||{},item:i,footer:n,fixedItemHeight:void 0!==s}));class J extends l{constructor(e){super(e),this.itemRenderer=(e=>this.props.item(e.index)),this.state=J.getDerivedStateFromProps(this.props,C(e))}static getDerivedStateFromProps(e,t){t.subscriptions.unsubscribe();const r=new c;return e.endReached&&r.add(t.endReached$.subscribe(e.endReached)),e.scrollingStateChange&&r.add(t.isScrolling$.subscribe(e.scrollingStateChange)),e.topItems&&t.topItemCount$.next(e.topItems),t.totalCount$.next(e.totalCount),{...t,subscriptions:r}}render(){return e.createElement(G,{contextValue:this.state,style:this.props.style,item:this.itemRenderer,footer:this.props.footer,itemHeight:this.props.itemHeight})}}class K extends l{constructor(e){super(e),this.itemRender=(e=>"group"==e.type?this.props.group(e.groupIndex):this.props.item(e.transposedIndex,e.groupIndex)),this.state=K.getDerivedStateFromProps(this.props,C(e))}static getDerivedStateFromProps(e,t){t.subscriptions.unsubscribe();const r=new c;return e.endReached&&r.add(t.endReached$.subscribe(e.endReached)),e.scrollingStateChange&&r.add(t.isScrolling$.subscribe(e.scrollingStateChange)),t.groupCounts$.next(e.groupCounts),{...t,subscriptions:r}}render(){return e.createElement(G,{contextValue:this.state,style:this.props.style,item:this.itemRender,footer:this.props.footer,itemHeight:this.props.itemHeight})}}export{K as GroupedVirtuoso,J as Virtuoso,G as VirtuosoPresentation}; | ||
//# sourceMappingURL=react-virtuoso.es.production.js.map |
@@ -10,2 +10,4 @@ (function (global, factory) { | ||
const VirtuosoContext = React.createContext(undefined); | ||
class NilNode { | ||
@@ -27,5 +29,11 @@ constructor() { | ||
} | ||
findWith() { | ||
return; | ||
} | ||
findMax() { | ||
return -Infinity; | ||
} | ||
findMaxValue() { | ||
return; | ||
} | ||
insert(key, value) { | ||
@@ -62,2 +70,7 @@ // eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
Object.freeze(NIL_NODE); | ||
class UnreachableCaseError extends Error { | ||
constructor(val) { | ||
super(`Unreachable case: ${val}`); | ||
} | ||
} | ||
class NonNilNode { | ||
@@ -114,2 +127,15 @@ constructor({ key, value, level, left = NIL_NODE, right = NIL_NODE }) { | ||
} | ||
findWith(callback) { | ||
const result = callback(this.value); | ||
switch (result) { | ||
case -1: | ||
return this.left.findWith(callback); | ||
case 0: | ||
return [this.key, this.value]; | ||
case 1: | ||
return this.right.findWith(callback); | ||
default: | ||
throw new UnreachableCaseError(result); | ||
} | ||
} | ||
findMax(key) { | ||
@@ -130,2 +156,17 @@ if (this.key === key) { | ||
} | ||
findMaxValue(key) { | ||
if (this.key === key) { | ||
return this.value; | ||
} | ||
if (this.key < key) { | ||
const rightValue = this.right.findMaxValue(key); | ||
if (rightValue === undefined) { | ||
return this.value; | ||
} | ||
else { | ||
return rightValue; | ||
} | ||
} | ||
return this.left.findMaxValue(key); | ||
} | ||
insert(key, value) { | ||
@@ -323,2 +364,11 @@ if (key === this.key) { | ||
} | ||
findMaxValue(key) { | ||
if (this.empty()) { | ||
throw new Error('Searching for max value in an empty tree'); | ||
} | ||
return this.root.findMaxValue(key); | ||
} | ||
findWith(callback) { | ||
return this.root.findWith(callback); | ||
} | ||
insert(key, value) { | ||
@@ -375,2 +425,5 @@ return new AATree(this.root.insert(key, value)); | ||
} | ||
empty() { | ||
return this.rangeTree.empty(); | ||
} | ||
insert(start, end, size) { | ||
@@ -407,3 +460,3 @@ let tree = this.rangeTree; | ||
if (rangeEnd > end && end >= rangeStart) { | ||
if (rangeValue !== size) { | ||
if (rangeValue !== size && !isNaN(rangeValue)) { | ||
tree = tree.insert(end + 1, rangeValue); | ||
@@ -418,2 +471,34 @@ } | ||
} | ||
insertException(index, value) { | ||
if (this.empty()) { | ||
return new OffsetList(this.rangeTree.insert(1, NaN).insert(index, value)); | ||
} | ||
else { | ||
return this.insert(index, index, value); | ||
} | ||
} | ||
offsetOf(index) { | ||
if (this.offsetTree.empty()) { | ||
return 0; | ||
} | ||
const find = (value) => { | ||
if (value.startIndex > index) | ||
return -1; | ||
if (value.endIndex < index) | ||
return 1; | ||
return 0; | ||
}; | ||
const offsetRange = this.offsetTree.findWith(find); | ||
if (offsetRange) { | ||
const [offset, { startIndex, size }] = offsetRange; | ||
return offset + (index - startIndex) * size; | ||
} | ||
else { | ||
throw new Error(`Requested offset outside of the known ones, index: ${index}`); | ||
} | ||
} | ||
itemAt(index) { | ||
const size = this.rangeTree.findMaxValue(index); | ||
return { index, size, offset: NaN }; | ||
} | ||
indexRange(startIndex, endIndex) { | ||
@@ -454,3 +539,11 @@ if (endIndex === 0) { | ||
} | ||
startIndex = Math.max(minIndex, startIndex); | ||
if (startIndex < minIndex) { | ||
offset += (minIndex - startIndex) * size; | ||
startIndex = minIndex; | ||
} | ||
// we don't know the size of this range - terminate with a probe item | ||
if (isNaN(size)) { | ||
result.push({ index: startIndex, size: 0, offset }); | ||
return result; | ||
} | ||
endIndex = Math.min(endIndex, maxIndex); | ||
@@ -476,23 +569,84 @@ for (let i = startIndex; i <= endIndex; i++) { | ||
} | ||
getOffsets(indices) { | ||
let tree = AATree.empty(); | ||
indices.forEach(index => { | ||
const offset = this.offsetOf(index); | ||
tree = tree.insert(offset, index); | ||
}); | ||
return new IndexList(tree); | ||
} | ||
} | ||
class IndexList { | ||
constructor(tree) { | ||
this.tree = tree; | ||
} | ||
findMaxValue(offset) { | ||
return this.tree.findMaxValue(offset); | ||
} | ||
empty() { | ||
return this.tree.empty(); | ||
} | ||
} | ||
class StubIndexTransposer { | ||
transpose(items) { | ||
return items.map(item => { | ||
return { | ||
groupIndex: 0, | ||
index: item.index, | ||
offset: item.offset, | ||
size: item.size, | ||
transposedIndex: item.index, | ||
type: 'item', | ||
}; | ||
}); | ||
} | ||
} | ||
class GroupIndexTransposer { | ||
constructor(counts) { | ||
this.count = counts.reduce((acc, groupCount) => acc + groupCount + 1, 0); | ||
let tree = AATree.empty(); | ||
let groupIndex = 0; | ||
let total = 0; | ||
for (let groupCount of counts) { | ||
tree = tree.insert(total, [groupIndex, total]); | ||
groupIndex++; | ||
total += groupCount + 1; | ||
} | ||
this.tree = tree; | ||
} | ||
totalCount() { | ||
return this.count; | ||
} | ||
transpose(items) { | ||
return items.map(item => { | ||
const groupMatch = this.tree.find(item.index); | ||
if (groupMatch) { | ||
return { | ||
groupIndex: groupMatch[0], | ||
index: item.index, | ||
offset: item.offset, | ||
size: item.size, | ||
type: 'group', | ||
}; | ||
} | ||
const [groupIndex] = this.tree.findMaxValue(item.index); | ||
return { | ||
groupIndex: groupIndex, | ||
index: item.index, | ||
offset: item.offset, | ||
size: item.size, | ||
transposedIndex: item.index - groupIndex - 1, | ||
type: 'item', | ||
}; | ||
}); | ||
} | ||
groupIndices() { | ||
return this.tree.keys(); | ||
} | ||
} | ||
const getListTop = (items) => (items.length > 0 ? items[0].offset : 0); | ||
const mapToTotal = ([offsetList, totalCount]) => offsetList.total(totalCount - 1); | ||
const listScanner = overscan => (items, [[viewportHeight, scrollTop, topListHeight, listHeight, footerHeight, minIndex, totalCount], offsetList]) => { | ||
const listTop = getListTop(items); | ||
const listBottom = listTop - scrollTop + listHeight - footerHeight - topListHeight; | ||
const maxIndex = Math.max(totalCount - 1, 0); | ||
if (listBottom < viewportHeight) { | ||
const startOffset = Math.max(scrollTop + topListHeight, topListHeight); | ||
const endOffset = scrollTop + viewportHeight + overscan * 2 - 1; | ||
return offsetList.range(startOffset, endOffset, minIndex, maxIndex); | ||
} | ||
if (listTop > scrollTop + topListHeight) { | ||
const startOffset = Math.max(scrollTop + topListHeight - overscan * 2, topListHeight); | ||
const endOffset = scrollTop + viewportHeight - 1; | ||
return offsetList.range(startOffset, endOffset, minIndex, maxIndex); | ||
} | ||
return items; | ||
}; | ||
const VirtuosoStore = ({ overscan = 0, totalCount, topItems = 0, itemHeight }) => { | ||
const VirtuosoStore = ({ overscan = 0, totalCount = 0, itemHeight }) => { | ||
const viewportHeight$ = new rxjs.BehaviorSubject(0); | ||
@@ -504,5 +658,7 @@ const listHeight$ = new rxjs.BehaviorSubject(0); | ||
const totalCount$ = new rxjs.BehaviorSubject(totalCount); | ||
const topItemCount$ = new rxjs.BehaviorSubject(topItems); | ||
const groupCounts$ = new rxjs.Subject(); | ||
const topItemCount$ = new rxjs.Subject(); | ||
const isScrolling$ = new rxjs.BehaviorSubject(false); | ||
let initialOffsetList = OffsetList.create(); | ||
const stickyItems$ = new rxjs.BehaviorSubject([]); | ||
if (itemHeight) { | ||
@@ -513,4 +669,9 @@ initialOffsetList = initialOffsetList.insert(0, 0, itemHeight); | ||
if (!itemHeight) { | ||
itemHeights$.pipe(operators.withLatestFrom(offsetList$)).subscribe(([heights, offsetList]) => { | ||
const newList = heights.reduce((list, { start, end, size }) => list.insert(start, end, size), offsetList); | ||
itemHeights$.pipe(operators.withLatestFrom(offsetList$, stickyItems$)).subscribe(([heights, offsetList, stickyItems]) => { | ||
const newList = heights.reduce((list, { start, end, size }) => { | ||
if (start === end && stickyItems.indexOf(start) > -1) { | ||
return list.insertException(start, size); | ||
} | ||
return list.insert(start, end, size); | ||
}, offsetList); | ||
if (newList !== offsetList) { | ||
@@ -521,10 +682,52 @@ offsetList$.next(newList); | ||
} | ||
let transposer = new StubIndexTransposer(); | ||
const listScanner = overscan => (items, [[viewportHeight, scrollTop, topListHeight, listHeight, footerHeight, minIndex, totalCount], offsetList]) => { | ||
const listTop = getListTop(items); | ||
const listBottom = listTop - scrollTop + listHeight - footerHeight - topListHeight; | ||
const maxIndex = Math.max(totalCount - 1, 0); | ||
const topIndexOutOfRange = items.length > 0 && items[0].index < minIndex; | ||
if (listBottom < viewportHeight || topIndexOutOfRange) { | ||
const startOffset = Math.max(scrollTop + topListHeight, topListHeight); | ||
const endOffset = scrollTop + viewportHeight + overscan * 2 - 1; | ||
return transposer.transpose(offsetList.range(startOffset, endOffset, minIndex, maxIndex)); | ||
} | ||
if (listTop > scrollTop + topListHeight) { | ||
const startOffset = Math.max(scrollTop + topListHeight - overscan * 2, topListHeight); | ||
const endOffset = scrollTop + viewportHeight - 1; | ||
return transposer.transpose(offsetList.range(startOffset, endOffset, minIndex, maxIndex)); | ||
} | ||
return items; | ||
}; | ||
groupCounts$.subscribe(counts => { | ||
transposer = new GroupIndexTransposer(counts); | ||
totalCount$.next(transposer.totalCount()); | ||
stickyItems$.next(transposer.groupIndices()); | ||
}); | ||
const totalListHeight$ = rxjs.combineLatest(offsetList$, totalCount$).pipe(operators.map(mapToTotal)); | ||
const totalHeight$ = rxjs.combineLatest(totalListHeight$, footerHeight$).pipe(operators.map(([totalListHeight, footerHeight]) => totalListHeight + footerHeight)); | ||
const topList$ = rxjs.combineLatest(offsetList$, topItemCount$, totalCount$).pipe(operators.map(([offsetList, topItemCount, totalCount]) => { | ||
const stickyItemsIndexList$ = rxjs.combineLatest(offsetList$, stickyItems$).pipe(operators.map(([offsetList, stickyItems]) => { | ||
return offsetList.getOffsets(stickyItems); | ||
})); | ||
const topList$ = new rxjs.BehaviorSubject([]); | ||
rxjs.combineLatest(offsetList$, topItemCount$, totalCount$) | ||
.pipe(operators.filter(params => params[1] > 0), operators.map(([offsetList, topItemCount, totalCount]) => { | ||
const endIndex = Math.max(0, Math.min(topItemCount - 1, totalCount)); | ||
return offsetList.indexRange(0, endIndex); | ||
})); | ||
return transposer.transpose(offsetList.indexRange(0, endIndex)); | ||
})) | ||
.subscribe(topList$); | ||
rxjs.combineLatest(offsetList$, stickyItemsIndexList$, scrollTop$) | ||
.pipe(operators.filter(params => !params[1].empty() && !params[0].empty()), operators.withLatestFrom(topList$), operators.map(([[offsetList, stickyItemsIndexList, scrollTop], topList]) => { | ||
const currentStickyItem = stickyItemsIndexList.findMaxValue(scrollTop); | ||
if (topList.length === 1 && topList[0].index === currentStickyItem) { | ||
return topList; | ||
} | ||
const item = offsetList.itemAt(currentStickyItem); | ||
return transposer.transpose([item]); | ||
}), operators.distinctUntilChanged()) | ||
.subscribe(topList$); | ||
const topListHeight$ = topList$.pipe(operators.map(items => items.reduce((total, item) => total + item.size, 0)), operators.distinctUntilChanged(), operators.auditTime(0)); | ||
const list$ = rxjs.combineLatest(viewportHeight$.pipe(operators.distinctUntilChanged()), scrollTop$.pipe(operators.distinctUntilChanged()), topListHeight$.pipe(operators.distinctUntilChanged()), listHeight$.pipe(operators.distinctUntilChanged()), footerHeight$.pipe(operators.distinctUntilChanged()), topItemCount$, totalCount$).pipe(operators.withLatestFrom(offsetList$), operators.scan(listScanner(overscan), []), operators.distinctUntilChanged()); | ||
const minListIndex$ = topList$.pipe(operators.map(topList => { | ||
return topList.length && topList[topList.length - 1].index + 1; | ||
}), operators.distinctUntilChanged()); | ||
const list$ = rxjs.combineLatest(viewportHeight$.pipe(operators.distinctUntilChanged()), scrollTop$.pipe(operators.distinctUntilChanged()), topListHeight$.pipe(operators.distinctUntilChanged()), listHeight$.pipe(operators.distinctUntilChanged()), footerHeight$.pipe(operators.distinctUntilChanged()), minListIndex$, totalCount$).pipe(operators.withLatestFrom(offsetList$), operators.scan(listScanner(overscan), []), operators.distinctUntilChanged()); | ||
const endReached$ = list$.pipe(operators.map(items => (items.length ? items[items.length - 1].index : 0)), operators.scan((prev, current) => Math.max(prev, current)), operators.distinctUntilChanged()); | ||
@@ -538,2 +741,3 @@ const listOffset$ = rxjs.combineLatest(list$, scrollTop$, topListHeight$).pipe(operators.map(([items, scrollTop, topListHeight]) => getListTop(items) - scrollTop - topListHeight)); | ||
.subscribe(isScrolling$); | ||
const subscriptions = new rxjs.Subscription(); | ||
return { | ||
@@ -547,2 +751,4 @@ // input | ||
viewportHeight$, | ||
topItemCount$, | ||
groupCounts$, | ||
// output | ||
@@ -555,7 +761,7 @@ list$, | ||
isScrolling$, | ||
subscriptions, | ||
stickyItems$, | ||
}; | ||
}; | ||
const VirtuosoContext = React.createContext(undefined); | ||
const useHeight = (observer$, onMount = null) => { | ||
@@ -609,60 +815,86 @@ const ref = React.useRef(null); | ||
const buildSizes = items => { | ||
const results = []; | ||
for (const item of items) { | ||
const index = parseInt(item.dataset.index); | ||
const size = item.offsetHeight; | ||
if (results.length === 0 || results[results.length - 1].size !== size) { | ||
results.push({ start: index, end: index, size }); | ||
class ItemHeightPublisher { | ||
constructor(itemHeights$) { | ||
this.itemElements = []; | ||
this.trackRef = (ref) => { | ||
if (ref) { | ||
this.itemElements.push(ref); | ||
} | ||
}; | ||
this.itemHeights$ = itemHeights$; | ||
} | ||
publishSizes(items) { | ||
const results = []; | ||
for (const item of items) { | ||
const index = parseInt(item.dataset.index); | ||
const size = item.offsetHeight; | ||
if (results.length === 0 || results[results.length - 1].size !== size) { | ||
results.push({ start: index, end: index, size }); | ||
} | ||
else { | ||
results[results.length - 1].end++; | ||
} | ||
} | ||
else { | ||
results[results.length - 1].end++; | ||
} | ||
this.itemHeights$.next(results); | ||
} | ||
return results; | ||
}; | ||
const itemRenderer = ({ items, itemAttributes, transform, render }) => { | ||
const style = { | ||
transform, | ||
zIndex: transform !== '' ? 2 : undefined, | ||
position: transform !== '' ? 'relative' : undefined, | ||
}; | ||
init() { | ||
this.observer = new ResizeObserver((entries) => { | ||
this.publishSizes(entries.map(({ target }) => target)); | ||
}); | ||
this.publishSizes(this.itemElements); | ||
this.itemElements.map(item => { | ||
this.observer.observe(item); | ||
}); | ||
} | ||
destroy() { | ||
this.itemElements = []; | ||
this.observer.disconnect(); | ||
} | ||
getItemAttributes() { | ||
return (item) => { | ||
return { | ||
'data-index': item.index, | ||
'data-known-size': item.size, | ||
ref: this.trackRef, | ||
}; | ||
}; | ||
} | ||
} | ||
const itemRenderer = ({ items, itemAttributes, render, getStyle }) => { | ||
return items.map(item => { | ||
return (React__default.createElement("div", Object.assign({ key: item.index }, itemAttributes && itemAttributes(item), { style: style }), render(item.index))); | ||
return (React__default.createElement("div", Object.assign({ key: item.index }, itemAttributes && itemAttributes(item), { style: getStyle(item.index) }), render(item))); | ||
}); | ||
}; | ||
const VirtuosoList = React__default.memo(({ list$, transform = '', item: itemRenderProp }) => { | ||
const VirtuosoVariableList = React__default.memo(({ items, render, getStyle }) => { | ||
const { itemHeights$ } = React.useContext(VirtuosoContext); | ||
const items = useObservable(list$, []); | ||
const itemRefs = React.useRef([]); | ||
const heightPublisher = React.useRef(new ItemHeightPublisher(itemHeights$)); | ||
React.useLayoutEffect(() => { | ||
let observer = new ResizeObserver((entries) => { | ||
itemHeights$.next(buildSizes(entries.map(({ target }) => target))); | ||
}); | ||
itemHeights$.next(buildSizes(itemRefs.current)); | ||
itemRefs.current.map(item => { | ||
observer.observe(item); | ||
}); | ||
heightPublisher.current.init(); | ||
return () => { | ||
itemRefs.current = []; | ||
observer.disconnect(); | ||
heightPublisher.current.destroy(); | ||
}; | ||
}, [items]); | ||
const itemCallbackRef = React.useCallback(ref => { | ||
if (ref) { | ||
itemRefs.current.push(ref); | ||
} | ||
}, [items]); | ||
const itemAttributes = (item) => { | ||
return { | ||
'data-index': item.index, | ||
'data-known-size': item.size, | ||
ref: itemCallbackRef, | ||
}; | ||
}; | ||
return React__default.createElement(React__default.Fragment, null, itemRenderer({ items, transform, render: itemRenderProp, itemAttributes })); | ||
return React__default.createElement(React__default.Fragment, null, itemRenderer({ items, render, itemAttributes: heightPublisher.current.getItemAttributes(), getStyle })); | ||
}); | ||
const VirtuosoFixedList = React__default.memo(({ list$, transform = '', item: itemRenderProp }) => { | ||
const VirtuosoStaticList = React__default.memo(({ items, render, getStyle }) => { | ||
return React__default.createElement(React__default.Fragment, null, itemRenderer({ items, render, getStyle })); | ||
}); | ||
const VirtuosoList = React__default.memo(({ list$, transform = '', render, fixedItemHeight }) => { | ||
const { stickyItems$ } = React.useContext(VirtuosoContext); | ||
const items = useObservable(list$, []); | ||
return React__default.createElement(React__default.Fragment, null, itemRenderer({ items, transform, render: itemRenderProp })); | ||
const stickyItems = useObservable(stickyItems$, []); | ||
const getStyle = React.useCallback((index) => { | ||
const pinned = stickyItems.some(stickyItemIndex => stickyItemIndex === index); | ||
const style = { | ||
transform, | ||
zIndex: pinned ? 2 : undefined, | ||
position: pinned ? 'relative' : undefined, | ||
}; | ||
return style; | ||
}, [stickyItems, transform]); | ||
if (items.length === 0) { | ||
return null; | ||
} | ||
return fixedItemHeight ? (React__default.createElement(VirtuosoStaticList, Object.assign({}, { items, render, getStyle }))) : (React__default.createElement(VirtuosoVariableList, Object.assign({}, { items, render, getStyle }))); | ||
}); | ||
@@ -685,3 +917,3 @@ | ||
}; | ||
const VirtuosoView = ({ style, footer, item, topItemCount, fixedItemHeight }) => { | ||
const VirtuosoView = ({ style, footer, item, fixedItemHeight }) => { | ||
const { listHeight$, viewportHeight$, listOffset$, list$, topList$ } = React.useContext(VirtuosoContext); | ||
@@ -701,4 +933,4 @@ const listOffset = useObservable(listOffset$, 0); | ||
React__default.createElement("div", { ref: listCallbackRef }, | ||
topItemCount > 0 && React__default.createElement(VirtuosoList, { "list$": topList$, transform: topTransform, item: item }), | ||
fixedItemHeight ? (React__default.createElement(VirtuosoFixedList, { "list$": list$, item: item })) : (React__default.createElement(VirtuosoList, { "list$": list$, item: item })), | ||
React__default.createElement(VirtuosoList, { "list$": topList$, transform: topTransform, render: item, fixedItemHeight: fixedItemHeight }), | ||
React__default.createElement(VirtuosoList, { "list$": list$, render: item, fixedItemHeight: fixedItemHeight }), | ||
footer && React__default.createElement(VirtuosoFooter, { footer: footer })))), | ||
@@ -708,29 +940,69 @@ React__default.createElement(VirtuosoFiller, null))); | ||
const VirtuosoPresentation = ({ contextValue, style, item, footer, itemHeight, }) => { | ||
return (React__default.createElement(VirtuosoContext.Provider, { value: contextValue }, | ||
React__default.createElement(VirtuosoView, { style: style || {}, item: item, footer: footer, fixedItemHeight: itemHeight !== undefined }))); | ||
}; | ||
class Virtuoso extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.state = VirtuosoStore(props); | ||
this.itemRenderer = (item) => { | ||
return this.props.item(item.index); | ||
}; | ||
this.state = Virtuoso.getDerivedStateFromProps(this.props, VirtuosoStore(props)); | ||
} | ||
static getDerivedStateFromProps(props, state) { | ||
state.subscriptions.unsubscribe(); | ||
const nextSubscriptions = new rxjs.Subscription(); | ||
if (props.endReached) { | ||
this.state.endReached$.subscribe(props.endReached); | ||
nextSubscriptions.add(state.endReached$.subscribe(props.endReached)); | ||
} | ||
if (props.scrollingStateChange) { | ||
this.state.isScrolling$.subscribe(props.scrollingStateChange); | ||
nextSubscriptions.add(state.isScrolling$.subscribe(props.scrollingStateChange)); | ||
} | ||
if (props.topItems) { | ||
state.topItemCount$.next(props.topItems); | ||
} | ||
state.totalCount$.next(props.totalCount); | ||
return { ...state, subscriptions: nextSubscriptions }; | ||
} | ||
render() { | ||
return (React__default.createElement(VirtuosoPresentation, { contextValue: this.state, style: this.props.style, item: this.itemRenderer, footer: this.props.footer, itemHeight: this.props.itemHeight })); | ||
} | ||
} | ||
class GroupedVirtuoso extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.itemRender = (item) => { | ||
if (item.type == 'group') { | ||
return this.props.group(item.groupIndex); | ||
} | ||
else { | ||
return this.props.item(item.transposedIndex, item.groupIndex); | ||
} | ||
}; | ||
this.state = GroupedVirtuoso.getDerivedStateFromProps(this.props, VirtuosoStore(props)); | ||
} | ||
static getDerivedStateFromProps(props, state) { | ||
state.totalCount$.next(props.totalCount); | ||
return null; | ||
state.subscriptions.unsubscribe(); | ||
const nextSubscriptions = new rxjs.Subscription(); | ||
if (props.endReached) { | ||
nextSubscriptions.add(state.endReached$.subscribe(props.endReached)); | ||
} | ||
if (props.scrollingStateChange) { | ||
nextSubscriptions.add(state.isScrolling$.subscribe(props.scrollingStateChange)); | ||
} | ||
state.groupCounts$.next(props.groupCounts); | ||
return { ...state, subscriptions: nextSubscriptions }; | ||
} | ||
render() { | ||
return (React__default.createElement(VirtuosoContext.Provider, { value: this.state }, | ||
React__default.createElement(VirtuosoView, { style: this.props.style || {}, item: this.props.item, footer: this.props.footer, topItemCount: this.props.topItems, fixedItemHeight: this.props.itemHeight !== undefined }))); | ||
return (React__default.createElement(VirtuosoPresentation, { contextValue: this.state, style: this.props.style, item: this.itemRender, footer: this.props.footer, itemHeight: this.props.itemHeight })); | ||
} | ||
} | ||
Virtuoso.defaultProps = { | ||
topItems: 0, | ||
}; | ||
exports.GroupedVirtuoso = GroupedVirtuoso; | ||
exports.Virtuoso = Virtuoso; | ||
exports.VirtuosoPresentation = VirtuosoPresentation; | ||
})); | ||
//# sourceMappingURL=react-virtuoso.umd.development.js.map |
@@ -1,2 +0,2 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react"),require("rxjs"),require("rxjs/operators"),require("resize-observer-polyfill")):"function"==typeof define&&define.amd?define(["exports","react","rxjs","rxjs/operators","resize-observer-polyfill"],t):(e=e||self,t(e["react-virtuoso"]={},e.React,e.rxjs,e.operators,e.ResizeObserver))}(this,function(e,t,i,r,n){"use strict";var s="default"in t?t.default:t;n=n&&n.hasOwnProperty("default")?n.default:n;const l=new class{constructor(){this.level=0}rebalance(){return this}adjust(){return this}remove(){return this}find(){}findMax(){return-Infinity}insert(e,t){return new o({key:e,value:t,level:1})}walkWithin(){return[]}walk(){return[]}ranges(){return[]}rangesWithin(){return[]}empty(){return!0}isSingle(){return!0}isInvariant(){return!0}keys(){return[]}};Object.freeze(l);class o{constructor({key:e,value:t,level:i,left:r=l,right:n=l}){this.key=e,this.value=t,this.level=i,this.left=r,this.right=n}remove(e){const{left:t,right:i}=this;if(e===this.key){if(t.empty())return i;if(i.empty())return t;{const[e,i]=t.last();return this.clone({key:e,value:i,left:t.deleteLast()}).adjust()}}return e<this.key?this.clone({left:t.remove(e)}).adjust():this.clone({right:i.remove(e)}).adjust()}empty(){return!1}find(e){return e===this.key?this.value:e<this.key?this.left.find(e):this.right.find(e)}findMax(e){if(this.key===e)return e;if(this.key<e){const t=this.right.findMax(e);return-Infinity===t?this.key:t}return this.left.findMax(e)}insert(e,t){return e===this.key?this.clone({key:e,value:t}):e<this.key?this.clone({left:this.left.insert(e,t)}).rebalance():this.clone({right:this.right.insert(e,t)}).rebalance()}walkWithin(e,t){const{key:i,value:r}=this;let n=[];return i>e&&(n=n.concat(this.left.walkWithin(e,t))),i>=e&&i<=t&&n.push({key:i,value:r}),i<=t&&(n=n.concat(this.right.walkWithin(e,t))),n}walk(){return[...this.left.walk(),{key:this.key,value:this.value},...this.right.walk()]}last(){return this.right.empty()?[this.key,this.value]:this.right.last()}deleteLast(){return this.right.empty()?this.left:this.clone({right:this.right.deleteLast()}).adjust()}clone(e){return new o({key:void 0!==e.key?e.key:this.key,value:void 0!==e.value?e.value:this.value,level:void 0!==e.level?e.level:this.level,left:void 0!==e.left?e.left:this.left,right:void 0!==e.right?e.right:this.right})}isSingle(){return this.level>this.right.level}rebalance(){return this.skew().split()}adjust(){const{left:e,right:t,level:i}=this;if(t.level>=i-1&&e.level>=i-1)return this;if(i>t.level+1){if(e.isSingle())return this.clone({level:i-1}).skew();if(e.empty()||e.right.empty())throw new Error("Unexpected empty nodes");return e.right.clone({left:e.clone({right:e.right.left}),right:this.clone({left:e.right.right,level:i-1}),level:i})}if(this.isSingle())return this.clone({level:i-1}).split();if(t.empty()||t.left.empty())throw new Error("Unexpected empty nodes");{const e=t.left,r=e.isSingle()?t.level-1:t.level;return e.clone({left:this.clone({right:e.left,level:i-1}),right:t.clone({left:e.right,level:r}).split(),level:e.level+1})}}isInvariant(){const{left:e,right:t,level:i}=this;return i===e.level+1&&((i===t.level||i===t.level+1)&&(!(!t.empty()&&i<=t.right.level)&&(e.isInvariant()&&t.isInvariant())))}keys(){return[...this.left.keys(),this.key,...this.right.keys()]}ranges(){return this.toRanges(this.walk())}rangesWithin(e,t){return this.toRanges(this.walkWithin(e,t))}toRanges(e){if(0===e.length)return[];const t=e[0];let{key:i,value:r}=t;const n=[];for(let t=1;t<=e.length;t++){let s=e[t],l=s?s.key-1:Infinity;n.push({start:i,end:l,value:r}),s&&(i=s.key,r=s.value)}return n}split(){const{right:e,level:t}=this;return e.empty()||e.right.empty()||e.level!=t||e.right.level!=t?this:e.clone({left:this.clone({right:e.left}),level:t+1})}skew(){const{left:e}=this;return e.empty()||e.level!==this.level?this:e.clone({right:this.clone({left:e.right})})}}class a{constructor(e){this.root=e}static empty(){return new a(l)}find(e){return this.root.find(e)}findMax(e){return this.root.findMax(e)}insert(e,t){return new a(this.root.insert(e,t))}remove(e){return new a(this.root.remove(e))}empty(){return this.root.empty()}keys(){return this.root.keys()}walk(){return this.root.walk()}walkWithin(e,t){let i=this.root.findMax(e);return this.root.walkWithin(i,t)}ranges(){return this.root.ranges()}rangesWithin(e,t){let i=this.root.findMax(e);return this.root.rangesWithin(i,t)}isInvariant(){return this.root.isInvariant()}}class h{static create(){return new h(a.empty())}constructor(e){this.rangeTree=e;let t=a.empty(),i=0;for(const{start:r,end:n,value:s}of e.ranges())t=t.insert(i,{startIndex:r,endIndex:n,size:s}),Infinity!==n&&(i+=(n-r+1)*s);this.offsetTree=t}insert(e,t,i){let r=this.rangeTree;if(r.empty())return new h(r.insert(0,i));const n=r.rangesWithin(e-1,t+1);if(n.some(r=>r.start===e&&(r.end===t||Infinity===r.end)&&r.value===i))return this;let s=!1,l=!1;for(const{start:e,end:o,value:a}of n)s?(t>=e||i===a)&&(r=r.remove(e)):(l=a!==i,s=!0),o>t&&t>=e&&a!==i&&(r=r.insert(t+1,a));return l&&(r=r.insert(e,i)),r===this.rangeTree?this:new h(r)}indexRange(e,t){if(0===t)return[];if(this.rangeTree.empty())return[{index:0,size:0,offset:NaN}];const i=this.rangeTree.rangesWithin(e,t),r=[];for(const n of i){const i=Math.max(e,n.start),s=void 0===n.end?Infinity:n.end,l=Math.min(t,s);for(let e=i;e<=l;e++)r.push({index:e,size:n.value,offset:NaN})}return r}range(e,t,i=0,r=Infinity){if(0===r)return[];if(this.offsetTree.empty())return[{index:0,size:0,offset:0}];const n=this.offsetTree.rangesWithin(e,t),s=[];for(let{start:l,value:{startIndex:o,endIndex:a,size:h}}of n){let n=l,u=o;l<e&&(u+=Math.floor((e-l)/h),n+=(u-o)*h),u=Math.max(i,u),a=Math.min(a,r);for(let e=u;e<=a&&!(n>t);e++)s.push({index:e,size:h,offset:n}),n+=h}return s}total(e){const t=this.rangeTree.rangesWithin(0,e);let i=0;for(let{start:r,end:n,value:s}of t)n=Math.min(n,e),i+=(n-r+1)*s;return i}}const u=e=>e.length>0?e[0].offset:0,c=([e,t])=>e.total(t-1),f=({overscan:e=0,totalCount:t,topItems:n=0,itemHeight:s})=>{const l=new i.BehaviorSubject(0),o=new i.BehaviorSubject(0),a=new i.BehaviorSubject(0),f=new i.BehaviorSubject(0),d=new i.Subject,p=new i.BehaviorSubject(t),g=new i.BehaviorSubject(n),m=new i.BehaviorSubject(!1);let v=h.create();s&&(v=v.insert(0,0,s));const y=new i.BehaviorSubject(v);s||d.pipe(r.withLatestFrom(y)).subscribe(([e,t])=>{const i=e.reduce((e,{start:t,end:i,size:r})=>e.insert(t,i,r),t);i!==t&&y.next(i)});const k=i.combineLatest(y,p).pipe(r.map(c)),x=i.combineLatest(k,f).pipe(r.map(([e,t])=>e+t)),b=i.combineLatest(y,g,p).pipe(r.map(([e,t,i])=>{const r=Math.max(0,Math.min(t-1,i));return e.indexRange(0,r)})),w=b.pipe(r.map(e=>e.reduce((e,t)=>e+t.size,0)),r.distinctUntilChanged(),r.auditTime(0)),$=i.combineLatest(l.pipe(r.distinctUntilChanged()),a.pipe(r.distinctUntilChanged()),w.pipe(r.distinctUntilChanged()),o.pipe(r.distinctUntilChanged()),f.pipe(r.distinctUntilChanged()),g,p).pipe(r.withLatestFrom(y),r.scan((e=>(t,[[i,r,n,s,l,o,a],h])=>{const c=u(t),f=c-r+s-l-n,d=Math.max(a-1,0);if(f<i){const t=Math.max(r+n,n),s=r+i+2*e-1;return h.range(t,s,o,d)}if(c>r+n){const t=Math.max(r+n-2*e,n),s=r+i-1;return h.range(t,s,o,d)}return t})(e),[]),r.distinctUntilChanged()),C=$.pipe(r.map(e=>e.length?e[e.length-1].index:0),r.scan((e,t)=>Math.max(e,t)),r.distinctUntilChanged()),I=i.combineLatest($,a,w).pipe(r.map(([e,t,i])=>u(e)-t-i));return a.pipe(r.mapTo(!0),r.skip(1)).subscribe(m),a.pipe(r.debounceTime(200),r.mapTo(!1),r.skip(1)).subscribe(m),{totalCount$:p,footerHeight$:f,itemHeights$:d,listHeight$:o,scrollTop$:a,viewportHeight$:l,list$:$,listOffset$:I,totalHeight$:x,topList$:b,endReached$:C,isScrolling$:m}},d=t.createContext(void 0),p=(e,i=null)=>{const r=t.useRef(null),s=new n(([{contentRect:{height:t}}])=>{e.next(t)});return e=>{e?(s.observe(e),null!==i&&i(e),r.current=e):(s.unobserve(r.current),r.current=null)}};function g(e,i){const[n,s]=t.useState(i);return t.useLayoutEffect(()=>{e.pipe(r.distinctUntilChanged()).subscribe(s)},[]),n}const m={height:"40rem",overflowY:"auto",WebkitOverflowScrolling:"touch",position:"relative",outline:"none"},v=({children:e,style:i})=>{const{scrollTop$:r}=t.useContext(d),n=t.useCallback(e=>{r.next(e.target.scrollTop)},[]),l=t.useCallback(e=>{e&&e.addEventListener("scroll",n,{passive:!0})},[]);return s.createElement("div",{ref:l,style:{...m,...i},tabIndex:0},e)},y=e=>{const t=[];for(const i of e){const e=parseInt(i.dataset.index),r=i.offsetHeight;0===t.length||t[t.length-1].size!==r?t.push({start:e,end:e,size:r}):t[t.length-1].end++}return t},k=({items:e,itemAttributes:t,transform:i,render:r})=>{const n={transform:i,zIndex:""!==i?2:void 0,position:""!==i?"relative":void 0};return e.map(e=>s.createElement("div",Object.assign({key:e.index},t&&t(e),{style:n}),r(e.index)))},x=s.memo(({list$:e,transform:i="",item:r})=>{const{itemHeights$:l}=t.useContext(d),o=g(e,[]),a=t.useRef([]);t.useLayoutEffect(()=>{let e=new n(e=>{l.next(y(e.map(({target:e})=>e)))});return l.next(y(a.current)),a.current.map(t=>{e.observe(t)}),()=>{a.current=[],e.disconnect()}},[o]);const h=t.useCallback(e=>{e&&a.current.push(e)},[o]);return s.createElement(s.Fragment,null,k({items:o,transform:i,render:r,itemAttributes:e=>({"data-index":e.index,"data-known-size":e.size,ref:h})}))}),b=s.memo(({list$:e,transform:t="",item:i})=>{const r=g(e,[]);return s.createElement(s.Fragment,null,k({items:r,transform:t,render:i}))}),w=()=>{const e=g(t.useContext(d).totalHeight$,0);return s.createElement("div",{style:{height:`${e}px`,position:"absolute",top:0}}," ")},$=({footer:e})=>{const i=p(t.useContext(d).footerHeight$);return s.createElement("footer",{ref:i},e())},C={top:0,position:"sticky",height:"100%",overflow:"hidden",WebkitBackfaceVisibility:"hidden"},I=({style:e,footer:i,item:r,topItemCount:n,fixedItemHeight:l})=>{const{listHeight$:o,viewportHeight$:a,listOffset$:h,list$:u,topList$:c}=t.useContext(d),f=g(h,0),m=p(o),y=p(a,e=>{""===e.style.position&&(e.style.position="-webkit-sticky")}),k=`translateY(${f}px)`,I=`translateY(${-f}px)`;return s.createElement(v,{style:e},s.createElement("div",{style:C,ref:y},s.createElement("div",{style:{transform:k}},s.createElement("div",{ref:m},n>0&&s.createElement(x,{list$:c,transform:I,item:r}),s.createElement(l?b:x,{list$:u,item:r}),i&&s.createElement($,{footer:i})))),s.createElement(w,null))};class j extends t.PureComponent{constructor(e){super(e),this.state=f(e),e.endReached&&this.state.endReached$.subscribe(e.endReached),e.scrollingStateChange&&this.state.isScrolling$.subscribe(e.scrollingStateChange)}static getDerivedStateFromProps(e,t){return t.totalCount$.next(e.totalCount),null}render(){return s.createElement(d.Provider,{value:this.state},s.createElement(I,{style:this.props.style||{},item:this.props.item,footer:this.props.footer,topItemCount:this.props.topItems,fixedItemHeight:void 0!==this.props.itemHeight}))}}j.defaultProps={topItems:0},e.Virtuoso=j}); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react"),require("rxjs"),require("rxjs/operators"),require("resize-observer-polyfill")):"function"==typeof define&&define.amd?define(["exports","react","rxjs","rxjs/operators","resize-observer-polyfill"],t):(e=e||self,t(e["react-virtuoso"]={},e.React,e.rxjs,e.operators,e.ResizeObserver))}(this,function(e,t,i,r,n){"use strict";var s="default"in t?t.default:t;n=n&&n.hasOwnProperty("default")?n.default:n;const o=t.createContext(void 0);const l=new class{constructor(){this.level=0}rebalance(){return this}adjust(){return this}remove(){return this}find(){}findWith(){}findMax(){return-Infinity}findMaxValue(){}insert(e,t){return new h({key:e,value:t,level:1})}walkWithin(){return[]}walk(){return[]}ranges(){return[]}rangesWithin(){return[]}empty(){return!0}isSingle(){return!0}isInvariant(){return!0}keys(){return[]}};Object.freeze(l);class a extends Error{constructor(e){super(`Unreachable case: ${e}`)}}class h{constructor({key:e,value:t,level:i,left:r=l,right:n=l}){this.key=e,this.value=t,this.level=i,this.left=r,this.right=n}remove(e){const{left:t,right:i}=this;if(e===this.key){if(t.empty())return i;if(i.empty())return t;{const[e,i]=t.last();return this.clone({key:e,value:i,left:t.deleteLast()}).adjust()}}return e<this.key?this.clone({left:t.remove(e)}).adjust():this.clone({right:i.remove(e)}).adjust()}empty(){return!1}find(e){return e===this.key?this.value:e<this.key?this.left.find(e):this.right.find(e)}findWith(e){const t=e(this.value);switch(t){case-1:return this.left.findWith(e);case 0:return[this.key,this.value];case 1:return this.right.findWith(e);default:throw new a(t)}}findMax(e){if(this.key===e)return e;if(this.key<e){const t=this.right.findMax(e);return-Infinity===t?this.key:t}return this.left.findMax(e)}findMaxValue(e){if(this.key===e)return this.value;if(this.key<e){const t=this.right.findMaxValue(e);return void 0===t?this.value:t}return this.left.findMaxValue(e)}insert(e,t){return e===this.key?this.clone({key:e,value:t}):e<this.key?this.clone({left:this.left.insert(e,t)}).rebalance():this.clone({right:this.right.insert(e,t)}).rebalance()}walkWithin(e,t){const{key:i,value:r}=this;let n=[];return i>e&&(n=n.concat(this.left.walkWithin(e,t))),i>=e&&i<=t&&n.push({key:i,value:r}),i<=t&&(n=n.concat(this.right.walkWithin(e,t))),n}walk(){return[...this.left.walk(),{key:this.key,value:this.value},...this.right.walk()]}last(){return this.right.empty()?[this.key,this.value]:this.right.last()}deleteLast(){return this.right.empty()?this.left:this.clone({right:this.right.deleteLast()}).adjust()}clone(e){return new h({key:void 0!==e.key?e.key:this.key,value:void 0!==e.value?e.value:this.value,level:void 0!==e.level?e.level:this.level,left:void 0!==e.left?e.left:this.left,right:void 0!==e.right?e.right:this.right})}isSingle(){return this.level>this.right.level}rebalance(){return this.skew().split()}adjust(){const{left:e,right:t,level:i}=this;if(t.level>=i-1&&e.level>=i-1)return this;if(i>t.level+1){if(e.isSingle())return this.clone({level:i-1}).skew();if(e.empty()||e.right.empty())throw new Error("Unexpected empty nodes");return e.right.clone({left:e.clone({right:e.right.left}),right:this.clone({left:e.right.right,level:i-1}),level:i})}if(this.isSingle())return this.clone({level:i-1}).split();if(t.empty()||t.left.empty())throw new Error("Unexpected empty nodes");{const e=t.left,r=e.isSingle()?t.level-1:t.level;return e.clone({left:this.clone({right:e.left,level:i-1}),right:t.clone({left:e.right,level:r}).split(),level:e.level+1})}}isInvariant(){const{left:e,right:t,level:i}=this;return i===e.level+1&&((i===t.level||i===t.level+1)&&(!(!t.empty()&&i<=t.right.level)&&(e.isInvariant()&&t.isInvariant())))}keys(){return[...this.left.keys(),this.key,...this.right.keys()]}ranges(){return this.toRanges(this.walk())}rangesWithin(e,t){return this.toRanges(this.walkWithin(e,t))}toRanges(e){if(0===e.length)return[];const t=e[0];let{key:i,value:r}=t;const n=[];for(let t=1;t<=e.length;t++){let s=e[t],o=s?s.key-1:Infinity;n.push({start:i,end:o,value:r}),s&&(i=s.key,r=s.value)}return n}split(){const{right:e,level:t}=this;return e.empty()||e.right.empty()||e.level!=t||e.right.level!=t?this:e.clone({left:this.clone({right:e.left}),level:t+1})}skew(){const{left:e}=this;return e.empty()||e.level!==this.level?this:e.clone({right:this.clone({left:e.right})})}}class u{constructor(e){this.root=e}static empty(){return new u(l)}find(e){return this.root.find(e)}findMax(e){return this.root.findMax(e)}findMaxValue(e){if(this.empty())throw new Error("Searching for max value in an empty tree");return this.root.findMaxValue(e)}findWith(e){return this.root.findWith(e)}insert(e,t){return new u(this.root.insert(e,t))}remove(e){return new u(this.root.remove(e))}empty(){return this.root.empty()}keys(){return this.root.keys()}walk(){return this.root.walk()}walkWithin(e,t){let i=this.root.findMax(e);return this.root.walkWithin(i,t)}ranges(){return this.root.ranges()}rangesWithin(e,t){let i=this.root.findMax(e);return this.root.rangesWithin(i,t)}isInvariant(){return this.root.isInvariant()}}class c{static create(){return new c(u.empty())}constructor(e){this.rangeTree=e;let t=u.empty(),i=0;for(const{start:r,end:n,value:s}of e.ranges())t=t.insert(i,{startIndex:r,endIndex:n,size:s}),Infinity!==n&&(i+=(n-r+1)*s);this.offsetTree=t}empty(){return this.rangeTree.empty()}insert(e,t,i){let r=this.rangeTree;if(r.empty())return new c(r.insert(0,i));const n=r.rangesWithin(e-1,t+1);if(n.some(r=>r.start===e&&(r.end===t||Infinity===r.end)&&r.value===i))return this;let s=!1,o=!1;for(const{start:e,end:l,value:a}of n)s?(t>=e||i===a)&&(r=r.remove(e)):(o=a!==i,s=!0),l>t&&t>=e&&(a===i||isNaN(a)||(r=r.insert(t+1,a)));return o&&(r=r.insert(e,i)),r===this.rangeTree?this:new c(r)}insertException(e,t){return this.empty()?new c(this.rangeTree.insert(1,NaN).insert(e,t)):this.insert(e,e,t)}offsetOf(e){if(this.offsetTree.empty())return 0;const t=this.offsetTree.findWith(t=>t.startIndex>e?-1:t.endIndex<e?1:0);if(t){const[i,{startIndex:r,size:n}]=t;return i+(e-r)*n}throw new Error(`Requested offset outside of the known ones, index: ${e}`)}itemAt(e){const t=this.rangeTree.findMaxValue(e);return{index:e,size:t,offset:NaN}}indexRange(e,t){if(0===t)return[];if(this.rangeTree.empty())return[{index:0,size:0,offset:NaN}];const i=this.rangeTree.rangesWithin(e,t),r=[];for(const n of i){const i=Math.max(e,n.start),s=void 0===n.end?Infinity:n.end,o=Math.min(t,s);for(let e=i;e<=o;e++)r.push({index:e,size:n.value,offset:NaN})}return r}range(e,t,i=0,r=Infinity){if(0===r)return[];if(this.offsetTree.empty())return[{index:0,size:0,offset:0}];const n=this.offsetTree.rangesWithin(e,t),s=[];for(let{start:o,value:{startIndex:l,endIndex:a,size:h}}of n){let n=o,u=l;if(o<e&&(u+=Math.floor((e-o)/h),n+=(u-l)*h),u<i&&(n+=(i-u)*h,u=i),isNaN(h))return s.push({index:u,size:0,offset:n}),s;a=Math.min(a,r);for(let e=u;e<=a&&!(n>t);e++)s.push({index:e,size:h,offset:n}),n+=h}return s}total(e){const t=this.rangeTree.rangesWithin(0,e);let i=0;for(let{start:r,end:n,value:s}of t)n=Math.min(n,e),i+=(n-r+1)*s;return i}getOffsets(e){let t=u.empty();return e.forEach(e=>{const i=this.offsetOf(e);t=t.insert(i,e)}),new f(t)}}class f{constructor(e){this.tree=e}findMaxValue(e){return this.tree.findMaxValue(e)}empty(){return this.tree.empty()}}class d{transpose(e){return e.map(e=>({groupIndex:0,index:e.index,offset:e.offset,size:e.size,transposedIndex:e.index,type:"item"}))}}class p{constructor(e){this.count=e.reduce((e,t)=>e+t+1,0);let t=u.empty(),i=0,r=0;for(let n of e)t=t.insert(r,[i,r]),i++,r+=n+1;this.tree=t}totalCount(){return this.count}transpose(e){return e.map(e=>{const t=this.tree.find(e.index);if(t)return{groupIndex:t[0],index:e.index,offset:e.offset,size:e.size,type:"group"};const[i]=this.tree.findMaxValue(e.index);return{groupIndex:i,index:e.index,offset:e.offset,size:e.size,transposedIndex:e.index-i-1,type:"item"}})}groupIndices(){return this.tree.keys()}}const m=e=>e.length>0?e[0].offset:0,g=([e,t])=>e.total(t-1),v=({overscan:e=0,totalCount:t=0,itemHeight:n})=>{const s=new i.BehaviorSubject(0),o=new i.BehaviorSubject(0),l=new i.BehaviorSubject(0),a=new i.BehaviorSubject(0),h=new i.Subject,u=new i.BehaviorSubject(t),f=new i.Subject,v=new i.Subject,y=new i.BehaviorSubject(!1);let x=c.create();const b=new i.BehaviorSubject([]);n&&(x=x.insert(0,0,n));const k=new i.BehaviorSubject(x);n||h.pipe(r.withLatestFrom(k,b)).subscribe(([e,t,i])=>{const r=e.reduce((e,{start:t,end:r,size:n})=>t===r&&i.indexOf(t)>-1?e.insertException(t,n):e.insert(t,r,n),t);r!==t&&k.next(r)});let w=new d;f.subscribe(e=>{w=new p(e),u.next(w.totalCount()),b.next(w.groupIndices())});const I=i.combineLatest(k,u).pipe(r.map(g)),S=i.combineLatest(I,a).pipe(r.map(([e,t])=>e+t)),$=i.combineLatest(k,b).pipe(r.map(([e,t])=>e.getOffsets(t))),C=new i.BehaviorSubject([]);i.combineLatest(k,v,u).pipe(r.filter(e=>e[1]>0),r.map(([e,t,i])=>{const r=Math.max(0,Math.min(t-1,i));return w.transpose(e.indexRange(0,r))})).subscribe(C),i.combineLatest(k,$,l).pipe(r.filter(e=>!e[1].empty()&&!e[0].empty()),r.withLatestFrom(C),r.map(([[e,t,i],r])=>{const n=t.findMaxValue(i);if(1===r.length&&r[0].index===n)return r;const s=e.itemAt(n);return w.transpose([s])}),r.distinctUntilChanged()).subscribe(C);const E=C.pipe(r.map(e=>e.reduce((e,t)=>e+t.size,0)),r.distinctUntilChanged(),r.auditTime(0)),M=C.pipe(r.map(e=>e.length&&e[e.length-1].index+1),r.distinctUntilChanged()),z=i.combineLatest(s.pipe(r.distinctUntilChanged()),l.pipe(r.distinctUntilChanged()),E.pipe(r.distinctUntilChanged()),o.pipe(r.distinctUntilChanged()),a.pipe(r.distinctUntilChanged()),M,u).pipe(r.withLatestFrom(k),r.scan((e=>(t,[[i,r,n,s,o,l,a],h])=>{const u=m(t),c=u-r+s-o-n,f=Math.max(a-1,0),d=t.length>0&&t[0].index<l;if(c<i||d){const t=Math.max(r+n,n),s=r+i+2*e-1;return w.transpose(h.range(t,s,l,f))}if(u>r+n){const t=Math.max(r+n-2*e,n),s=r+i-1;return w.transpose(h.range(t,s,l,f))}return t})(e),[]),r.distinctUntilChanged()),j=z.pipe(r.map(e=>e.length?e[e.length-1].index:0),r.scan((e,t)=>Math.max(e,t)),r.distinctUntilChanged()),H=i.combineLatest(z,l,E).pipe(r.map(([e,t,i])=>m(e)-t-i));l.pipe(r.mapTo(!0),r.skip(1)).subscribe(y),l.pipe(r.debounceTime(200),r.mapTo(!1),r.skip(1)).subscribe(y);const R=new i.Subscription;return{totalCount$:u,footerHeight$:a,itemHeights$:h,listHeight$:o,scrollTop$:l,viewportHeight$:s,topItemCount$:v,groupCounts$:f,list$:z,listOffset$:H,totalHeight$:S,topList$:C,endReached$:j,isScrolling$:y,subscriptions:R,stickyItems$:b}},y=(e,i=null)=>{const r=t.useRef(null),s=new n(([{contentRect:{height:t}}])=>{e.next(t)});return e=>{e?(s.observe(e),null!==i&&i(e),r.current=e):(s.unobserve(r.current),r.current=null)}};function x(e,i){const[n,s]=t.useState(i);return t.useLayoutEffect(()=>{e.pipe(r.distinctUntilChanged()).subscribe(s)},[]),n}const b={height:"40rem",overflowY:"auto",WebkitOverflowScrolling:"touch",position:"relative",outline:"none"},k=({children:e,style:i})=>{const{scrollTop$:r}=t.useContext(o),n=t.useCallback(e=>{r.next(e.target.scrollTop)},[]),l=t.useCallback(e=>{e&&e.addEventListener("scroll",n,{passive:!0})},[]);return s.createElement("div",{ref:l,style:{...b,...i},tabIndex:0},e)};class w{constructor(e){this.itemElements=[],this.trackRef=(e=>{e&&this.itemElements.push(e)}),this.itemHeights$=e}publishSizes(e){const t=[];for(const i of e){const e=parseInt(i.dataset.index),r=i.offsetHeight;0===t.length||t[t.length-1].size!==r?t.push({start:e,end:e,size:r}):t[t.length-1].end++}this.itemHeights$.next(t)}init(){this.observer=new n(e=>{this.publishSizes(e.map(({target:e})=>e))}),this.publishSizes(this.itemElements),this.itemElements.map(e=>{this.observer.observe(e)})}destroy(){this.itemElements=[],this.observer.disconnect()}getItemAttributes(){return e=>({"data-index":e.index,"data-known-size":e.size,ref:this.trackRef})}}const I=({items:e,itemAttributes:t,render:i,getStyle:r})=>e.map(e=>s.createElement("div",Object.assign({key:e.index},t&&t(e),{style:r(e.index)}),i(e))),S=s.memo(({items:e,render:i,getStyle:r})=>{const{itemHeights$:n}=t.useContext(o),l=t.useRef(new w(n));return t.useLayoutEffect(()=>(l.current.init(),()=>{l.current.destroy()}),[e]),s.createElement(s.Fragment,null,I({items:e,render:i,itemAttributes:l.current.getItemAttributes(),getStyle:r}))}),$=s.memo(({items:e,render:t,getStyle:i})=>s.createElement(s.Fragment,null,I({items:e,render:t,getStyle:i}))),C=s.memo(({list$:e,transform:i="",render:r,fixedItemHeight:n})=>{const{stickyItems$:l}=t.useContext(o),a=x(e,[]),h=x(l,[]),u=t.useCallback(e=>{const t=h.some(t=>t===e),r={transform:i,zIndex:t?2:void 0,position:t?"relative":void 0};return r},[h,i]);return 0===a.length?null:s.createElement(n?$:S,Object.assign({},{items:a,render:r,getStyle:u}))}),E=()=>{const e=x(t.useContext(o).totalHeight$,0);return s.createElement("div",{style:{height:`${e}px`,position:"absolute",top:0}}," ")},M=({footer:e})=>{const i=y(t.useContext(o).footerHeight$);return s.createElement("footer",{ref:i},e())},z={top:0,position:"sticky",height:"100%",overflow:"hidden",WebkitBackfaceVisibility:"hidden"},j=({style:e,footer:i,item:r,fixedItemHeight:n})=>{const{listHeight$:l,viewportHeight$:a,listOffset$:h,list$:u,topList$:c}=t.useContext(o),f=x(h,0),d=y(l),p=y(a,e=>{""===e.style.position&&(e.style.position="-webkit-sticky")}),m=`translateY(${f}px)`,g=`translateY(${-f}px)`;return s.createElement(k,{style:e},s.createElement("div",{style:z,ref:p},s.createElement("div",{style:{transform:m}},s.createElement("div",{ref:d},s.createElement(C,{list$:c,transform:g,render:r,fixedItemHeight:n}),s.createElement(C,{list$:u,render:r,fixedItemHeight:n}),i&&s.createElement(M,{footer:i})))),s.createElement(E,null))},H=({contextValue:e,style:t,item:i,footer:r,itemHeight:n})=>s.createElement(o.Provider,{value:e},s.createElement(j,{style:t||{},item:i,footer:r,fixedItemHeight:void 0!==n}));class R extends t.PureComponent{constructor(e){super(e),this.itemRenderer=(e=>this.props.item(e.index)),this.state=R.getDerivedStateFromProps(this.props,v(e))}static getDerivedStateFromProps(e,t){t.subscriptions.unsubscribe();const r=new i.Subscription;return e.endReached&&r.add(t.endReached$.subscribe(e.endReached)),e.scrollingStateChange&&r.add(t.isScrolling$.subscribe(e.scrollingStateChange)),e.topItems&&t.topItemCount$.next(e.topItems),t.totalCount$.next(e.totalCount),{...t,subscriptions:r}}render(){return s.createElement(H,{contextValue:this.state,style:this.props.style,item:this.itemRenderer,footer:this.props.footer,itemHeight:this.props.itemHeight})}}class W extends t.PureComponent{constructor(e){super(e),this.itemRender=(e=>"group"==e.type?this.props.group(e.groupIndex):this.props.item(e.transposedIndex,e.groupIndex)),this.state=W.getDerivedStateFromProps(this.props,v(e))}static getDerivedStateFromProps(e,t){t.subscriptions.unsubscribe();const r=new i.Subscription;return e.endReached&&r.add(t.endReached$.subscribe(e.endReached)),e.scrollingStateChange&&r.add(t.isScrolling$.subscribe(e.scrollingStateChange)),t.groupCounts$.next(e.groupCounts),{...t,subscriptions:r}}render(){return s.createElement(H,{contextValue:this.state,style:this.props.style,item:this.itemRender,footer:this.props.footer,itemHeight:this.props.itemHeight})}}e.GroupedVirtuoso=W,e.Virtuoso=R,e.VirtuosoPresentation=H}); | ||
//# sourceMappingURL=react-virtuoso.umd.production.js.map |
@@ -1,35 +0,13 @@ | ||
import { ReactElement, PureComponent, CSSProperties } from 'react'; | ||
import { CSSProperties, PureComponent, ReactElement, FC } from 'react'; | ||
import { VirtuosoStore } from './VirtuosoStore'; | ||
import { Subscription } from 'rxjs'; | ||
import { ListItem } from './GroupIndexTransposer'; | ||
import { TRender } from './VirtuosoList'; | ||
export declare type VirtuosoState = ReturnType<typeof VirtuosoStore>; | ||
interface VirtuosoProps { | ||
/** | ||
* The total amount of items to project | ||
*/ | ||
export interface VirtuosoProps { | ||
totalCount: number; | ||
/** | ||
* The amount in (pixels) to add in addition to the screen size | ||
* @default 0 | ||
*/ | ||
overscan?: number; | ||
/** | ||
* The amount of items to pin at the top of the scroll | ||
* @default 0 | ||
*/ | ||
topItems?: number; | ||
/** | ||
* Content to be displayed at the bottom of the list | ||
*/ | ||
footer?: () => ReactElement; | ||
/** | ||
* Item renderer prop - accepts the item index. To increase performance, use React.memo for the child contents. | ||
*/ | ||
item: (index: number) => ReactElement; | ||
/** | ||
* Optional, use for performance boost. Sets the height of the each item to a fixed amount, | ||
* causing the list to skip the measurement operations. | ||
* Use this only if you are certain that the items will stay the same size regardless of their content, the screen size, etc. | ||
* Notice: If you don't get that right, the items won't overlap each other, but the list may not render fully | ||
* or the total scroll size might be wrong, causing some items to be hidden. | ||
* @default undefined | ||
*/ | ||
itemHeight?: number; | ||
@@ -40,10 +18,33 @@ endReached?: (index: number) => void; | ||
} | ||
interface TVirtuosoPresentationProps { | ||
contextValue: VirtuosoState; | ||
item: TRender; | ||
footer?: () => ReactElement; | ||
style?: CSSProperties; | ||
itemHeight?: number; | ||
} | ||
export declare const VirtuosoPresentation: FC<TVirtuosoPresentationProps>; | ||
export declare class Virtuoso extends PureComponent<VirtuosoProps, VirtuosoState> { | ||
static defaultProps: { | ||
topItems: number; | ||
constructor(props: VirtuosoProps); | ||
static getDerivedStateFromProps(props: VirtuosoProps, state: VirtuosoState): { | ||
subscriptions: Subscription; | ||
totalCount$: import("rxjs").BehaviorSubject<number>; | ||
footerHeight$: import("rxjs").BehaviorSubject<number>; | ||
itemHeights$: import("rxjs").Subject<import("./VirtuosoStore").ItemHeight[]>; | ||
listHeight$: import("rxjs").BehaviorSubject<number>; | ||
scrollTop$: import("rxjs").BehaviorSubject<number>; | ||
viewportHeight$: import("rxjs").BehaviorSubject<number>; | ||
topItemCount$: import("rxjs").Subject<number>; | ||
groupCounts$: import("rxjs").Subject<number[]>; | ||
list$: import("rxjs").Observable<ListItem[]>; | ||
listOffset$: import("rxjs").Observable<number>; | ||
totalHeight$: import("rxjs").Observable<number>; | ||
topList$: import("rxjs").BehaviorSubject<ListItem[]>; | ||
endReached$: import("rxjs").Observable<number>; | ||
isScrolling$: import("rxjs").BehaviorSubject<boolean>; | ||
stickyItems$: import("rxjs").BehaviorSubject<number[]>; | ||
}; | ||
constructor(props: VirtuosoProps); | ||
static getDerivedStateFromProps(props: VirtuosoProps, state: VirtuosoState): null; | ||
private itemRenderer; | ||
render(): JSX.Element; | ||
} | ||
export default Virtuoso; | ||
export {}; |
@@ -9,8 +9,12 @@ /// <reference types="react" /> | ||
viewportHeight$: import("rxjs").BehaviorSubject<number>; | ||
list$: import("rxjs").Observable<import("./OffsetList").Item[]>; | ||
topItemCount$: import("rxjs").Subject<number>; | ||
groupCounts$: import("rxjs").Subject<number[]>; | ||
list$: import("rxjs").Observable<import("./GroupIndexTransposer").ListItem[]>; | ||
listOffset$: import("rxjs").Observable<number>; | ||
totalHeight$: import("rxjs").Observable<number>; | ||
topList$: import("rxjs").Observable<import("./OffsetList").Item[]>; | ||
topList$: import("rxjs").BehaviorSubject<import("./GroupIndexTransposer").ListItem[]>; | ||
endReached$: import("rxjs").Observable<number>; | ||
isScrolling$: import("rxjs").BehaviorSubject<boolean>; | ||
subscriptions: import("rxjs").Subscription; | ||
stickyItems$: import("rxjs").BehaviorSubject<number[]>; | ||
} | undefined>; |
import React, { ReactElement } from 'react'; | ||
import { VirtuosoState } from './Virtuoso'; | ||
declare type ListObservables = Pick<VirtuosoState, 'list$'> & { | ||
import { ListItem } from './GroupIndexTransposer'; | ||
export declare type TRender = (item: ListItem) => ReactElement; | ||
declare type TListProps = Pick<VirtuosoState, 'list$'> & { | ||
fixedItemHeight: boolean; | ||
render: TRender; | ||
transform?: string; | ||
item: (index: number) => ReactElement; | ||
}; | ||
export declare const VirtuosoList: React.FC<ListObservables>; | ||
export declare const VirtuosoFixedList: React.FC<ListObservables>; | ||
export declare const VirtuosoList: React.FC<TListProps>; | ||
export {}; |
@@ -1,3 +0,3 @@ | ||
import { BehaviorSubject, Observable, Subject } from 'rxjs'; | ||
import { Item } from './OffsetList'; | ||
import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; | ||
import { ListItem } from './GroupIndexTransposer'; | ||
export interface ItemHeight { | ||
@@ -10,7 +10,7 @@ start: number; | ||
overscan?: number; | ||
totalCount: number; | ||
totalCount?: number; | ||
topItems?: number; | ||
itemHeight?: number; | ||
} | ||
declare const VirtuosoStore: ({ overscan, totalCount, topItems, itemHeight }: TVirtuosoConstructorParams) => { | ||
declare const VirtuosoStore: ({ overscan, totalCount, itemHeight }: TVirtuosoConstructorParams) => { | ||
totalCount$: BehaviorSubject<number>; | ||
@@ -22,9 +22,13 @@ footerHeight$: BehaviorSubject<number>; | ||
viewportHeight$: BehaviorSubject<number>; | ||
list$: Observable<Item[]>; | ||
topItemCount$: Subject<number>; | ||
groupCounts$: Subject<number[]>; | ||
list$: Observable<ListItem[]>; | ||
listOffset$: Observable<number>; | ||
totalHeight$: Observable<number>; | ||
topList$: Observable<Item[]>; | ||
topList$: BehaviorSubject<ListItem[]>; | ||
endReached$: Observable<number>; | ||
isScrolling$: BehaviorSubject<boolean>; | ||
subscriptions: Subscription; | ||
stickyItems$: BehaviorSubject<number[]>; | ||
}; | ||
export { VirtuosoStore }; |
import React, { ReactElement, CSSProperties } from 'react'; | ||
import { TRender } from './VirtuosoList'; | ||
export declare const VirtuosoView: React.FC<{ | ||
style: CSSProperties; | ||
footer: (() => ReactElement) | undefined; | ||
item: (index: number) => ReactElement; | ||
topItemCount: number; | ||
item: TRender; | ||
fixedItemHeight: boolean; | ||
}>; |
{ | ||
"name": "react-virtuoso", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"homepage": "https://virtuoso.dev", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -8,5 +8,6 @@ <img src="https://user-images.githubusercontent.com/13347/57673110-85aab180-7623-11e9-97b4-27bbdcf8cf40.png" width="292"> | ||
- Handles gracefully variable sized items; no manual measurements or hard-coding of item heights; | ||
- Supports grouping with sticky group headers; | ||
- Automatically handles content resizing; | ||
- Can optionally render footer at the end of the list; | ||
- Supports pinning of the first `N` items to the top of the list. | ||
- Can render footer at the end of the list; | ||
- Can pin the first `N` items to the top of the list. | ||
@@ -45,3 +46,3 @@ To see live examples, check the [storybook](//virtuoso.dev). | ||
## Add a Footer | ||
### Add a Footer | ||
@@ -51,8 +52,10 @@ The component accepts an optional `footer` [render property](https://reactjs.org/docs/render-props.html), the contents of which are rendered at the bottom of the list. The footer can be used to host a "load more" button or an indicator that the user has reached the end of the list. | ||
```jsx | ||
return ( | ||
<Virtuoso | ||
style={{ height: '300px', width: '500px' }} | ||
totalCount={100} | ||
item={Item} | ||
item={index => <div>Item {index}</div>} | ||
footer={() => <div>-- end reached --</div>} | ||
/> | ||
) | ||
``` | ||
@@ -67,3 +70,10 @@ | ||
```jsx | ||
<Virtuoso style={{ height: '500px', width: '500px' }} topItems={2} totalCount={100000} item={Item} /> | ||
return ( | ||
<Virtuoso | ||
style={{ height: '500px', width: '500px' }} | ||
topItems={2} | ||
totalCount={100000} | ||
item={index => <div>Item {index}</div>} | ||
/> | ||
) | ||
``` | ||
@@ -73,2 +83,37 @@ | ||
### Grouping | ||
The package exports two components - `Virtuoso` and `GroupedVirtuoso`. | ||
The grouped component is similar to the flat one, with the following differences: | ||
* Instead of `totalCount`, the component accepts `groupedCounts: number[]`, which specifies the amount of items in each group. | ||
For example, passing `[20, 30]` will cause the component to render two groups with 20 and 30 items respectively; | ||
* In addition the `item` render prop, the component requires an additional `group` render prop, | ||
which renders the group header. The property receives the zero-based group index as a parameter; | ||
* The `item` render prop gets called with an additional second parameter, `groupIndex: number`. | ||
```jsx | ||
// generate 100 groups with 10 items each | ||
const groupCounts = [] | ||
for (let index = 0; index < 100; index++) { | ||
groupCounts.push(10) | ||
} | ||
return ( | ||
<GroupedVirtuoso | ||
style={{ height: '500px', width: '500px' }} | ||
groupCounts={groupCounts} | ||
group={ index => <div>Group {index * 10} - {index * 10 + 10}</div> } | ||
item={ (index, groupIndex) => <div>Item {index} from group {groupIndex}</div> } | ||
/> | ||
) | ||
``` | ||
Check the | ||
[grouped numbers](//virtuoso.dev/?path=/story/grouping--grouped-load-on-demand), | ||
[grouped by first letter](//virtuoso.dev/?path=/story/grouping--grouped-by-first-letter) and | ||
[groups with load on demand](//virtuoso.dev/?path=/story/grouping--groups-with-load-on-demand) | ||
examples for working examples. | ||
## Tweaking Performance | ||
@@ -86,3 +131,3 @@ | ||
## Properties | ||
## Properties of the `Virtuoso` Component | ||
@@ -124,2 +169,36 @@ ### `total: number` | ||
## Properties of the `GroupedVirtuoso` Component | ||
### `groupCounts: number[]` | ||
Mandatory. Specifies the amount of items in each group (and, actually, how many groups are there). For example, passing `[20, 30]` will display 2 groups with 20 and 30 items each. | ||
### `item: (index: number, groupIndex: number) => ReactElement` | ||
Mandatory. Specifies how each item gets rendered. The callback receives the zero-based index of the item and the index of the group of the item. | ||
### `group: (groupIndex: number) => ReactElement` | ||
Mandatory. Specifies how each each group header gets rendered. The callback receives the zero-based index of the group. | ||
### `style?: CSSProperties` | ||
Works just like the `style` property of the flat component. | ||
### `footer?: () => ReactElement` | ||
Works just like the `footer` property of the flat component. | ||
### `overscan?: number` | ||
Works just like the `overscan` property of the flat component. | ||
### `endReached?: (index: number) => void` | ||
Works just like the `endReached` callback of the flat component. | ||
### `scrollingStateChange?: (isScrolling: boolean) => void` | ||
Works just like the `scrollingStateChange` callback of the flat component. | ||
## Gotchas | ||
@@ -126,0 +205,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
229
2
1
472191
27
2396