planning
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s

This commit is contained in:
2024-10-14 09:15:30 +02:00
parent bcba00a730
commit 6e64e138e2
21059 changed files with 2317811 additions and 1 deletions

244
node_modules/react-window/src/FixedSizeGrid.js generated vendored Normal file
View File

@@ -0,0 +1,244 @@
// @flow
import createGridComponent from './createGridComponent';
import type { Props, ScrollToAlign } from './createGridComponent';
const FixedSizeGrid = createGridComponent({
getColumnOffset: ({ columnWidth }: Props<any>, index: number): number =>
index * ((columnWidth: any): number),
getColumnWidth: ({ columnWidth }: Props<any>, index: number): number =>
((columnWidth: any): number),
getRowOffset: ({ rowHeight }: Props<any>, index: number): number =>
index * ((rowHeight: any): number),
getRowHeight: ({ rowHeight }: Props<any>, index: number): number =>
((rowHeight: any): number),
getEstimatedTotalHeight: ({ rowCount, rowHeight }: Props<any>) =>
((rowHeight: any): number) * rowCount,
getEstimatedTotalWidth: ({ columnCount, columnWidth }: Props<any>) =>
((columnWidth: any): number) * columnCount,
getOffsetForColumnAndAlignment: (
{ columnCount, columnWidth, width }: Props<any>,
columnIndex: number,
align: ScrollToAlign,
scrollLeft: number,
instanceProps: typeof undefined,
scrollbarSize: number
): number => {
const lastColumnOffset = Math.max(
0,
columnCount * ((columnWidth: any): number) - width
);
const maxOffset = Math.min(
lastColumnOffset,
columnIndex * ((columnWidth: any): number)
);
const minOffset = Math.max(
0,
columnIndex * ((columnWidth: any): number) -
width +
scrollbarSize +
((columnWidth: any): number)
);
if (align === 'smart') {
if (scrollLeft >= minOffset - width && scrollLeft <= maxOffset + width) {
align = 'auto';
} else {
align = 'center';
}
}
switch (align) {
case 'start':
return maxOffset;
case 'end':
return minOffset;
case 'center':
// "Centered" offset is usually the average of the min and max.
// But near the edges of the list, this doesn't hold true.
const middleOffset = Math.round(
minOffset + (maxOffset - minOffset) / 2
);
if (middleOffset < Math.ceil(width / 2)) {
return 0; // near the beginning
} else if (middleOffset > lastColumnOffset + Math.floor(width / 2)) {
return lastColumnOffset; // near the end
} else {
return middleOffset;
}
case 'auto':
default:
if (scrollLeft >= minOffset && scrollLeft <= maxOffset) {
return scrollLeft;
} else if (minOffset > maxOffset) {
// Because we only take into account the scrollbar size when calculating minOffset
// this value can be larger than maxOffset when at the end of the list
return minOffset;
} else if (scrollLeft < minOffset) {
return minOffset;
} else {
return maxOffset;
}
}
},
getOffsetForRowAndAlignment: (
{ rowHeight, height, rowCount }: Props<any>,
rowIndex: number,
align: ScrollToAlign,
scrollTop: number,
instanceProps: typeof undefined,
scrollbarSize: number
): number => {
const lastRowOffset = Math.max(
0,
rowCount * ((rowHeight: any): number) - height
);
const maxOffset = Math.min(
lastRowOffset,
rowIndex * ((rowHeight: any): number)
);
const minOffset = Math.max(
0,
rowIndex * ((rowHeight: any): number) -
height +
scrollbarSize +
((rowHeight: any): number)
);
if (align === 'smart') {
if (scrollTop >= minOffset - height && scrollTop <= maxOffset + height) {
align = 'auto';
} else {
align = 'center';
}
}
switch (align) {
case 'start':
return maxOffset;
case 'end':
return minOffset;
case 'center':
// "Centered" offset is usually the average of the min and max.
// But near the edges of the list, this doesn't hold true.
const middleOffset = Math.round(
minOffset + (maxOffset - minOffset) / 2
);
if (middleOffset < Math.ceil(height / 2)) {
return 0; // near the beginning
} else if (middleOffset > lastRowOffset + Math.floor(height / 2)) {
return lastRowOffset; // near the end
} else {
return middleOffset;
}
case 'auto':
default:
if (scrollTop >= minOffset && scrollTop <= maxOffset) {
return scrollTop;
} else if (minOffset > maxOffset) {
// Because we only take into account the scrollbar size when calculating minOffset
// this value can be larger than maxOffset when at the end of the list
return minOffset;
} else if (scrollTop < minOffset) {
return minOffset;
} else {
return maxOffset;
}
}
},
getColumnStartIndexForOffset: (
{ columnWidth, columnCount }: Props<any>,
scrollLeft: number
): number =>
Math.max(
0,
Math.min(
columnCount - 1,
Math.floor(scrollLeft / ((columnWidth: any): number))
)
),
getColumnStopIndexForStartIndex: (
{ columnWidth, columnCount, width }: Props<any>,
startIndex: number,
scrollLeft: number
): number => {
const left = startIndex * ((columnWidth: any): number);
const numVisibleColumns = Math.ceil(
(width + scrollLeft - left) / ((columnWidth: any): number)
);
return Math.max(
0,
Math.min(
columnCount - 1,
startIndex + numVisibleColumns - 1 // -1 is because stop index is inclusive
)
);
},
getRowStartIndexForOffset: (
{ rowHeight, rowCount }: Props<any>,
scrollTop: number
): number =>
Math.max(
0,
Math.min(rowCount - 1, Math.floor(scrollTop / ((rowHeight: any): number)))
),
getRowStopIndexForStartIndex: (
{ rowHeight, rowCount, height }: Props<any>,
startIndex: number,
scrollTop: number
): number => {
const top = startIndex * ((rowHeight: any): number);
const numVisibleRows = Math.ceil(
(height + scrollTop - top) / ((rowHeight: any): number)
);
return Math.max(
0,
Math.min(
rowCount - 1,
startIndex + numVisibleRows - 1 // -1 is because stop index is inclusive
)
);
},
initInstanceProps(props: Props<any>): any {
// Noop
},
shouldResetStyleCacheOnItemSizeChange: true,
validateProps: ({ columnWidth, rowHeight }: Props<any>): void => {
if (process.env.NODE_ENV !== 'production') {
if (typeof columnWidth !== 'number') {
throw Error(
'An invalid "columnWidth" prop has been specified. ' +
'Value should be a number. ' +
`"${
columnWidth === null ? 'null' : typeof columnWidth
}" was specified.`
);
}
if (typeof rowHeight !== 'number') {
throw Error(
'An invalid "rowHeight" prop has been specified. ' +
'Value should be a number. ' +
`"${rowHeight === null ? 'null' : typeof rowHeight}" was specified.`
);
}
}
},
});
export default FixedSizeGrid;

137
node_modules/react-window/src/FixedSizeList.js generated vendored Normal file
View File

@@ -0,0 +1,137 @@
// @flow
import createListComponent from './createListComponent';
import type { Props, ScrollToAlign } from './createListComponent';
type InstanceProps = any;
const FixedSizeList = createListComponent({
getItemOffset: ({ itemSize }: Props<any>, index: number): number =>
index * ((itemSize: any): number),
getItemSize: ({ itemSize }: Props<any>, index: number): number =>
((itemSize: any): number),
getEstimatedTotalSize: ({ itemCount, itemSize }: Props<any>) =>
((itemSize: any): number) * itemCount,
getOffsetForIndexAndAlignment: (
{ direction, height, itemCount, itemSize, layout, width }: Props<any>,
index: number,
align: ScrollToAlign,
scrollOffset: number,
instanceProps: InstanceProps,
scrollbarSize: number
): number => {
// TODO Deprecate direction "horizontal"
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
const size = (((isHorizontal ? width : height): any): number);
const lastItemOffset = Math.max(
0,
itemCount * ((itemSize: any): number) - size
);
const maxOffset = Math.min(
lastItemOffset,
index * ((itemSize: any): number)
);
const minOffset = Math.max(
0,
index * ((itemSize: any): number) -
size +
((itemSize: any): number) +
scrollbarSize
);
if (align === 'smart') {
if (
scrollOffset >= minOffset - size &&
scrollOffset <= maxOffset + size
) {
align = 'auto';
} else {
align = 'center';
}
}
switch (align) {
case 'start':
return maxOffset;
case 'end':
return minOffset;
case 'center': {
// "Centered" offset is usually the average of the min and max.
// But near the edges of the list, this doesn't hold true.
const middleOffset = Math.round(
minOffset + (maxOffset - minOffset) / 2
);
if (middleOffset < Math.ceil(size / 2)) {
return 0; // near the beginning
} else if (middleOffset > lastItemOffset + Math.floor(size / 2)) {
return lastItemOffset; // near the end
} else {
return middleOffset;
}
}
case 'auto':
default:
if (scrollOffset >= minOffset && scrollOffset <= maxOffset) {
return scrollOffset;
} else if (scrollOffset < minOffset) {
return minOffset;
} else {
return maxOffset;
}
}
},
getStartIndexForOffset: (
{ itemCount, itemSize }: Props<any>,
offset: number
): number =>
Math.max(
0,
Math.min(itemCount - 1, Math.floor(offset / ((itemSize: any): number)))
),
getStopIndexForStartIndex: (
{ direction, height, itemCount, itemSize, layout, width }: Props<any>,
startIndex: number,
scrollOffset: number
): number => {
// TODO Deprecate direction "horizontal"
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
const offset = startIndex * ((itemSize: any): number);
const size = (((isHorizontal ? width : height): any): number);
const numVisibleItems = Math.ceil(
(size + scrollOffset - offset) / ((itemSize: any): number)
);
return Math.max(
0,
Math.min(
itemCount - 1,
startIndex + numVisibleItems - 1 // -1 is because stop index is inclusive
)
);
},
initInstanceProps(props: Props<any>): any {
// Noop
},
shouldResetStyleCacheOnItemSizeChange: true,
validateProps: ({ itemSize }: Props<any>): void => {
if (process.env.NODE_ENV !== 'production') {
if (typeof itemSize !== 'number') {
throw Error(
'An invalid "itemSize" prop has been specified. ' +
'Value should be a number. ' +
`"${itemSize === null ? 'null' : typeof itemSize}" was specified.`
);
}
}
},
});
export default FixedSizeList;

507
node_modules/react-window/src/VariableSizeGrid.js generated vendored Normal file
View File

@@ -0,0 +1,507 @@
// @flow
import createGridComponent from './createGridComponent';
import type { Props, ScrollToAlign } from './createGridComponent';
const DEFAULT_ESTIMATED_ITEM_SIZE = 50;
type VariableSizeProps = {|
estimatedColumnWidth: number,
estimatedRowHeight: number,
...Props<any>,
|};
type itemSizeGetter = (index: number) => number;
type ItemType = 'column' | 'row';
type ItemMetadata = {|
offset: number,
size: number,
|};
type ItemMetadataMap = { [index: number]: ItemMetadata };
type InstanceProps = {|
columnMetadataMap: ItemMetadataMap,
estimatedColumnWidth: number,
estimatedRowHeight: number,
lastMeasuredColumnIndex: number,
lastMeasuredRowIndex: number,
rowMetadataMap: ItemMetadataMap,
|};
const getEstimatedTotalHeight = (
{ rowCount }: Props<any>,
{ rowMetadataMap, estimatedRowHeight, lastMeasuredRowIndex }: InstanceProps
) => {
let totalSizeOfMeasuredRows = 0;
// Edge case check for when the number of items decreases while a scroll is in progress.
// https://github.com/bvaughn/react-window/pull/138
if (lastMeasuredRowIndex >= rowCount) {
lastMeasuredRowIndex = rowCount - 1;
}
if (lastMeasuredRowIndex >= 0) {
const itemMetadata = rowMetadataMap[lastMeasuredRowIndex];
totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size;
}
const numUnmeasuredItems = rowCount - lastMeasuredRowIndex - 1;
const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedRowHeight;
return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems;
};
const getEstimatedTotalWidth = (
{ columnCount }: Props<any>,
{
columnMetadataMap,
estimatedColumnWidth,
lastMeasuredColumnIndex,
}: InstanceProps
) => {
let totalSizeOfMeasuredRows = 0;
// Edge case check for when the number of items decreases while a scroll is in progress.
// https://github.com/bvaughn/react-window/pull/138
if (lastMeasuredColumnIndex >= columnCount) {
lastMeasuredColumnIndex = columnCount - 1;
}
if (lastMeasuredColumnIndex >= 0) {
const itemMetadata = columnMetadataMap[lastMeasuredColumnIndex];
totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size;
}
const numUnmeasuredItems = columnCount - lastMeasuredColumnIndex - 1;
const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedColumnWidth;
return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems;
};
const getItemMetadata = (
itemType: ItemType,
props: Props<any>,
index: number,
instanceProps: InstanceProps
): ItemMetadata => {
let itemMetadataMap, itemSize, lastMeasuredIndex;
if (itemType === 'column') {
itemMetadataMap = instanceProps.columnMetadataMap;
itemSize = ((props.columnWidth: any): itemSizeGetter);
lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex;
} else {
itemMetadataMap = instanceProps.rowMetadataMap;
itemSize = ((props.rowHeight: any): itemSizeGetter);
lastMeasuredIndex = instanceProps.lastMeasuredRowIndex;
}
if (index > lastMeasuredIndex) {
let offset = 0;
if (lastMeasuredIndex >= 0) {
const itemMetadata = itemMetadataMap[lastMeasuredIndex];
offset = itemMetadata.offset + itemMetadata.size;
}
for (let i = lastMeasuredIndex + 1; i <= index; i++) {
let size = itemSize(i);
itemMetadataMap[i] = {
offset,
size,
};
offset += size;
}
if (itemType === 'column') {
instanceProps.lastMeasuredColumnIndex = index;
} else {
instanceProps.lastMeasuredRowIndex = index;
}
}
return itemMetadataMap[index];
};
const findNearestItem = (
itemType: ItemType,
props: Props<any>,
instanceProps: InstanceProps,
offset: number
) => {
let itemMetadataMap, lastMeasuredIndex;
if (itemType === 'column') {
itemMetadataMap = instanceProps.columnMetadataMap;
lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex;
} else {
itemMetadataMap = instanceProps.rowMetadataMap;
lastMeasuredIndex = instanceProps.lastMeasuredRowIndex;
}
const lastMeasuredItemOffset =
lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0;
if (lastMeasuredItemOffset >= offset) {
// If we've already measured items within this range just use a binary search as it's faster.
return findNearestItemBinarySearch(
itemType,
props,
instanceProps,
lastMeasuredIndex,
0,
offset
);
} else {
// If we haven't yet measured this high, fallback to an exponential search with an inner binary search.
// The exponential search avoids pre-computing sizes for the full set of items as a binary search would.
// The overall complexity for this approach is O(log n).
return findNearestItemExponentialSearch(
itemType,
props,
instanceProps,
Math.max(0, lastMeasuredIndex),
offset
);
}
};
const findNearestItemBinarySearch = (
itemType: ItemType,
props: Props<any>,
instanceProps: InstanceProps,
high: number,
low: number,
offset: number
): number => {
while (low <= high) {
const middle = low + Math.floor((high - low) / 2);
const currentOffset = getItemMetadata(
itemType,
props,
middle,
instanceProps
).offset;
if (currentOffset === offset) {
return middle;
} else if (currentOffset < offset) {
low = middle + 1;
} else if (currentOffset > offset) {
high = middle - 1;
}
}
if (low > 0) {
return low - 1;
} else {
return 0;
}
};
const findNearestItemExponentialSearch = (
itemType: ItemType,
props: Props<any>,
instanceProps: InstanceProps,
index: number,
offset: number
): number => {
const itemCount = itemType === 'column' ? props.columnCount : props.rowCount;
let interval = 1;
while (
index < itemCount &&
getItemMetadata(itemType, props, index, instanceProps).offset < offset
) {
index += interval;
interval *= 2;
}
return findNearestItemBinarySearch(
itemType,
props,
instanceProps,
Math.min(index, itemCount - 1),
Math.floor(index / 2),
offset
);
};
const getOffsetForIndexAndAlignment = (
itemType: ItemType,
props: Props<any>,
index: number,
align: ScrollToAlign,
scrollOffset: number,
instanceProps: InstanceProps,
scrollbarSize: number
): number => {
const size = itemType === 'column' ? props.width : props.height;
const itemMetadata = getItemMetadata(itemType, props, index, instanceProps);
// Get estimated total size after ItemMetadata is computed,
// To ensure it reflects actual measurements instead of just estimates.
const estimatedTotalSize =
itemType === 'column'
? getEstimatedTotalWidth(props, instanceProps)
: getEstimatedTotalHeight(props, instanceProps);
const maxOffset = Math.max(
0,
Math.min(estimatedTotalSize - size, itemMetadata.offset)
);
const minOffset = Math.max(
0,
itemMetadata.offset - size + scrollbarSize + itemMetadata.size
);
if (align === 'smart') {
if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) {
align = 'auto';
} else {
align = 'center';
}
}
switch (align) {
case 'start':
return maxOffset;
case 'end':
return minOffset;
case 'center':
return Math.round(minOffset + (maxOffset - minOffset) / 2);
case 'auto':
default:
if (scrollOffset >= minOffset && scrollOffset <= maxOffset) {
return scrollOffset;
} else if (minOffset > maxOffset) {
// Because we only take into account the scrollbar size when calculating minOffset
// this value can be larger than maxOffset when at the end of the list
return minOffset;
} else if (scrollOffset < minOffset) {
return minOffset;
} else {
return maxOffset;
}
}
};
const VariableSizeGrid = createGridComponent({
getColumnOffset: (
props: Props<any>,
index: number,
instanceProps: InstanceProps
): number => getItemMetadata('column', props, index, instanceProps).offset,
getColumnStartIndexForOffset: (
props: Props<any>,
scrollLeft: number,
instanceProps: InstanceProps
): number => findNearestItem('column', props, instanceProps, scrollLeft),
getColumnStopIndexForStartIndex: (
props: Props<any>,
startIndex: number,
scrollLeft: number,
instanceProps: InstanceProps
): number => {
const { columnCount, width } = props;
const itemMetadata = getItemMetadata(
'column',
props,
startIndex,
instanceProps
);
const maxOffset = scrollLeft + width;
let offset = itemMetadata.offset + itemMetadata.size;
let stopIndex = startIndex;
while (stopIndex < columnCount - 1 && offset < maxOffset) {
stopIndex++;
offset += getItemMetadata('column', props, stopIndex, instanceProps).size;
}
return stopIndex;
},
getColumnWidth: (
props: Props<any>,
index: number,
instanceProps: InstanceProps
): number => instanceProps.columnMetadataMap[index].size,
getEstimatedTotalHeight,
getEstimatedTotalWidth,
getOffsetForColumnAndAlignment: (
props: Props<any>,
index: number,
align: ScrollToAlign,
scrollOffset: number,
instanceProps: InstanceProps,
scrollbarSize: number
): number =>
getOffsetForIndexAndAlignment(
'column',
props,
index,
align,
scrollOffset,
instanceProps,
scrollbarSize
),
getOffsetForRowAndAlignment: (
props: Props<any>,
index: number,
align: ScrollToAlign,
scrollOffset: number,
instanceProps: InstanceProps,
scrollbarSize: number
): number =>
getOffsetForIndexAndAlignment(
'row',
props,
index,
align,
scrollOffset,
instanceProps,
scrollbarSize
),
getRowOffset: (
props: Props<any>,
index: number,
instanceProps: InstanceProps
): number => getItemMetadata('row', props, index, instanceProps).offset,
getRowHeight: (
props: Props<any>,
index: number,
instanceProps: InstanceProps
): number => instanceProps.rowMetadataMap[index].size,
getRowStartIndexForOffset: (
props: Props<any>,
scrollTop: number,
instanceProps: InstanceProps
): number => findNearestItem('row', props, instanceProps, scrollTop),
getRowStopIndexForStartIndex: (
props: Props<any>,
startIndex: number,
scrollTop: number,
instanceProps: InstanceProps
): number => {
const { rowCount, height } = props;
const itemMetadata = getItemMetadata(
'row',
props,
startIndex,
instanceProps
);
const maxOffset = scrollTop + height;
let offset = itemMetadata.offset + itemMetadata.size;
let stopIndex = startIndex;
while (stopIndex < rowCount - 1 && offset < maxOffset) {
stopIndex++;
offset += getItemMetadata('row', props, stopIndex, instanceProps).size;
}
return stopIndex;
},
initInstanceProps(props: Props<any>, instance: any): InstanceProps {
const {
estimatedColumnWidth,
estimatedRowHeight,
} = ((props: any): VariableSizeProps);
const instanceProps = {
columnMetadataMap: {},
estimatedColumnWidth: estimatedColumnWidth || DEFAULT_ESTIMATED_ITEM_SIZE,
estimatedRowHeight: estimatedRowHeight || DEFAULT_ESTIMATED_ITEM_SIZE,
lastMeasuredColumnIndex: -1,
lastMeasuredRowIndex: -1,
rowMetadataMap: {},
};
instance.resetAfterColumnIndex = (
columnIndex: number,
shouldForceUpdate?: boolean = true
) => {
instance.resetAfterIndices({ columnIndex, shouldForceUpdate });
};
instance.resetAfterRowIndex = (
rowIndex: number,
shouldForceUpdate?: boolean = true
) => {
instance.resetAfterIndices({ rowIndex, shouldForceUpdate });
};
instance.resetAfterIndices = ({
columnIndex,
rowIndex,
shouldForceUpdate = true,
}: {
columnIndex?: number,
rowIndex?: number,
shouldForceUpdate: boolean,
}) => {
if (typeof columnIndex === 'number') {
instanceProps.lastMeasuredColumnIndex = Math.min(
instanceProps.lastMeasuredColumnIndex,
columnIndex - 1
);
}
if (typeof rowIndex === 'number') {
instanceProps.lastMeasuredRowIndex = Math.min(
instanceProps.lastMeasuredRowIndex,
rowIndex - 1
);
}
// We could potentially optimize further by only evicting styles after this index,
// But since styles are only cached while scrolling is in progress-
// It seems an unnecessary optimization.
// It's unlikely that resetAfterIndex() will be called while a user is scrolling.
instance._getItemStyleCache(-1);
if (shouldForceUpdate) {
instance.forceUpdate();
}
};
return instanceProps;
},
shouldResetStyleCacheOnItemSizeChange: false,
validateProps: ({ columnWidth, rowHeight }: Props<any>): void => {
if (process.env.NODE_ENV !== 'production') {
if (typeof columnWidth !== 'function') {
throw Error(
'An invalid "columnWidth" prop has been specified. ' +
'Value should be a function. ' +
`"${
columnWidth === null ? 'null' : typeof columnWidth
}" was specified.`
);
} else if (typeof rowHeight !== 'function') {
throw Error(
'An invalid "rowHeight" prop has been specified. ' +
'Value should be a function. ' +
`"${rowHeight === null ? 'null' : typeof rowHeight}" was specified.`
);
}
}
},
});
export default VariableSizeGrid;

317
node_modules/react-window/src/VariableSizeList.js generated vendored Normal file
View File

@@ -0,0 +1,317 @@
// @flow
import createListComponent from './createListComponent';
import type { Props, ScrollToAlign } from './createListComponent';
const DEFAULT_ESTIMATED_ITEM_SIZE = 50;
type VariableSizeProps = {|
estimatedItemSize: number,
...Props<any>,
|};
type itemSizeGetter = (index: number) => number;
type ItemMetadata = {|
offset: number,
size: number,
|};
type InstanceProps = {|
itemMetadataMap: { [index: number]: ItemMetadata },
estimatedItemSize: number,
lastMeasuredIndex: number,
|};
const getItemMetadata = (
props: Props<any>,
index: number,
instanceProps: InstanceProps
): ItemMetadata => {
const { itemSize } = ((props: any): VariableSizeProps);
const { itemMetadataMap, lastMeasuredIndex } = instanceProps;
if (index > lastMeasuredIndex) {
let offset = 0;
if (lastMeasuredIndex >= 0) {
const itemMetadata = itemMetadataMap[lastMeasuredIndex];
offset = itemMetadata.offset + itemMetadata.size;
}
for (let i = lastMeasuredIndex + 1; i <= index; i++) {
let size = ((itemSize: any): itemSizeGetter)(i);
itemMetadataMap[i] = {
offset,
size,
};
offset += size;
}
instanceProps.lastMeasuredIndex = index;
}
return itemMetadataMap[index];
};
const findNearestItem = (
props: Props<any>,
instanceProps: InstanceProps,
offset: number
) => {
const { itemMetadataMap, lastMeasuredIndex } = instanceProps;
const lastMeasuredItemOffset =
lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0;
if (lastMeasuredItemOffset >= offset) {
// If we've already measured items within this range just use a binary search as it's faster.
return findNearestItemBinarySearch(
props,
instanceProps,
lastMeasuredIndex,
0,
offset
);
} else {
// If we haven't yet measured this high, fallback to an exponential search with an inner binary search.
// The exponential search avoids pre-computing sizes for the full set of items as a binary search would.
// The overall complexity for this approach is O(log n).
return findNearestItemExponentialSearch(
props,
instanceProps,
Math.max(0, lastMeasuredIndex),
offset
);
}
};
const findNearestItemBinarySearch = (
props: Props<any>,
instanceProps: InstanceProps,
high: number,
low: number,
offset: number
): number => {
while (low <= high) {
const middle = low + Math.floor((high - low) / 2);
const currentOffset = getItemMetadata(props, middle, instanceProps).offset;
if (currentOffset === offset) {
return middle;
} else if (currentOffset < offset) {
low = middle + 1;
} else if (currentOffset > offset) {
high = middle - 1;
}
}
if (low > 0) {
return low - 1;
} else {
return 0;
}
};
const findNearestItemExponentialSearch = (
props: Props<any>,
instanceProps: InstanceProps,
index: number,
offset: number
): number => {
const { itemCount } = props;
let interval = 1;
while (
index < itemCount &&
getItemMetadata(props, index, instanceProps).offset < offset
) {
index += interval;
interval *= 2;
}
return findNearestItemBinarySearch(
props,
instanceProps,
Math.min(index, itemCount - 1),
Math.floor(index / 2),
offset
);
};
const getEstimatedTotalSize = (
{ itemCount }: Props<any>,
{ itemMetadataMap, estimatedItemSize, lastMeasuredIndex }: InstanceProps
) => {
let totalSizeOfMeasuredItems = 0;
// Edge case check for when the number of items decreases while a scroll is in progress.
// https://github.com/bvaughn/react-window/pull/138
if (lastMeasuredIndex >= itemCount) {
lastMeasuredIndex = itemCount - 1;
}
if (lastMeasuredIndex >= 0) {
const itemMetadata = itemMetadataMap[lastMeasuredIndex];
totalSizeOfMeasuredItems = itemMetadata.offset + itemMetadata.size;
}
const numUnmeasuredItems = itemCount - lastMeasuredIndex - 1;
const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedItemSize;
return totalSizeOfMeasuredItems + totalSizeOfUnmeasuredItems;
};
const VariableSizeList = createListComponent({
getItemOffset: (
props: Props<any>,
index: number,
instanceProps: InstanceProps
): number => getItemMetadata(props, index, instanceProps).offset,
getItemSize: (
props: Props<any>,
index: number,
instanceProps: InstanceProps
): number => instanceProps.itemMetadataMap[index].size,
getEstimatedTotalSize,
getOffsetForIndexAndAlignment: (
props: Props<any>,
index: number,
align: ScrollToAlign,
scrollOffset: number,
instanceProps: InstanceProps,
scrollbarSize: number
): number => {
const { direction, height, layout, width } = props;
// TODO Deprecate direction "horizontal"
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
const size = (((isHorizontal ? width : height): any): number);
const itemMetadata = getItemMetadata(props, index, instanceProps);
// Get estimated total size after ItemMetadata is computed,
// To ensure it reflects actual measurements instead of just estimates.
const estimatedTotalSize = getEstimatedTotalSize(props, instanceProps);
const maxOffset = Math.max(
0,
Math.min(estimatedTotalSize - size, itemMetadata.offset)
);
const minOffset = Math.max(
0,
itemMetadata.offset - size + itemMetadata.size + scrollbarSize
);
if (align === 'smart') {
if (
scrollOffset >= minOffset - size &&
scrollOffset <= maxOffset + size
) {
align = 'auto';
} else {
align = 'center';
}
}
switch (align) {
case 'start':
return maxOffset;
case 'end':
return minOffset;
case 'center':
return Math.round(minOffset + (maxOffset - minOffset) / 2);
case 'auto':
default:
if (scrollOffset >= minOffset && scrollOffset <= maxOffset) {
return scrollOffset;
} else if (scrollOffset < minOffset) {
return minOffset;
} else {
return maxOffset;
}
}
},
getStartIndexForOffset: (
props: Props<any>,
offset: number,
instanceProps: InstanceProps
): number => findNearestItem(props, instanceProps, offset),
getStopIndexForStartIndex: (
props: Props<any>,
startIndex: number,
scrollOffset: number,
instanceProps: InstanceProps
): number => {
const { direction, height, itemCount, layout, width } = props;
// TODO Deprecate direction "horizontal"
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
const size = (((isHorizontal ? width : height): any): number);
const itemMetadata = getItemMetadata(props, startIndex, instanceProps);
const maxOffset = scrollOffset + size;
let offset = itemMetadata.offset + itemMetadata.size;
let stopIndex = startIndex;
while (stopIndex < itemCount - 1 && offset < maxOffset) {
stopIndex++;
offset += getItemMetadata(props, stopIndex, instanceProps).size;
}
return stopIndex;
},
initInstanceProps(props: Props<any>, instance: any): InstanceProps {
const { estimatedItemSize } = ((props: any): VariableSizeProps);
const instanceProps = {
itemMetadataMap: {},
estimatedItemSize: estimatedItemSize || DEFAULT_ESTIMATED_ITEM_SIZE,
lastMeasuredIndex: -1,
};
instance.resetAfterIndex = (
index: number,
shouldForceUpdate?: boolean = true
) => {
instanceProps.lastMeasuredIndex = Math.min(
instanceProps.lastMeasuredIndex,
index - 1
);
// We could potentially optimize further by only evicting styles after this index,
// But since styles are only cached while scrolling is in progress-
// It seems an unnecessary optimization.
// It's unlikely that resetAfterIndex() will be called while a user is scrolling.
instance._getItemStyleCache(-1);
if (shouldForceUpdate) {
instance.forceUpdate();
}
};
return instanceProps;
},
shouldResetStyleCacheOnItemSizeChange: false,
validateProps: ({ itemSize }: Props<any>): void => {
if (process.env.NODE_ENV !== 'production') {
if (typeof itemSize !== 'function') {
throw Error(
'An invalid "itemSize" prop has been specified. ' +
'Value should be a function. ' +
`"${itemSize === null ? 'null' : typeof itemSize}" was specified.`
);
}
}
},
});
export default VariableSizeList;

18
node_modules/react-window/src/areEqual.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
// @flow
import shallowDiffers from './shallowDiffers';
// Custom comparison function for React.memo().
// It knows to compare individual style props and ignore the wrapper object.
// See https://reactjs.org/docs/react-api.html#reactmemo
export default function areEqual(
prevProps: Object,
nextProps: Object
): boolean {
const { style: prevStyle, ...prevRest } = prevProps;
const { style: nextStyle, ...nextRest } = nextProps;
return (
!shallowDiffers(prevStyle, nextStyle) && !shallowDiffers(prevRest, nextRest)
);
}

919
node_modules/react-window/src/createGridComponent.js generated vendored Normal file
View File

@@ -0,0 +1,919 @@
// @flow
import memoizeOne from 'memoize-one';
import { createElement, PureComponent } from 'react';
import { cancelTimeout, requestTimeout } from './timer';
import { getScrollbarSize, getRTLOffsetType } from './domHelpers';
import type { TimeoutID } from './timer';
type Direction = 'ltr' | 'rtl';
export type ScrollToAlign = 'auto' | 'smart' | 'center' | 'start' | 'end';
type itemSize = number | ((index: number) => number);
type RenderComponentProps<T> = {|
columnIndex: number,
data: T,
isScrolling?: boolean,
rowIndex: number,
style: Object,
|};
export type RenderComponent<T> = React$ComponentType<
$Shape<RenderComponentProps<T>>
>;
type ScrollDirection = 'forward' | 'backward';
type OnItemsRenderedCallback = ({
overscanColumnStartIndex: number,
overscanColumnStopIndex: number,
overscanRowStartIndex: number,
overscanRowStopIndex: number,
visibleColumnStartIndex: number,
visibleColumnStopIndex: number,
visibleRowStartIndex: number,
visibleRowStopIndex: number,
}) => void;
type OnScrollCallback = ({
horizontalScrollDirection: ScrollDirection,
scrollLeft: number,
scrollTop: number,
scrollUpdateWasRequested: boolean,
verticalScrollDirection: ScrollDirection,
}) => void;
type ScrollEvent = SyntheticEvent<HTMLDivElement>;
type ItemStyleCache = { [key: string]: Object };
type OuterProps = {|
children: React$Node,
className: string | void,
onScroll: ScrollEvent => void,
style: {
[string]: mixed,
},
|};
type InnerProps = {|
children: React$Node,
style: {
[string]: mixed,
},
|};
export type Props<T> = {|
children: RenderComponent<T>,
className?: string,
columnCount: number,
columnWidth: itemSize,
direction: Direction,
height: number,
initialScrollLeft?: number,
initialScrollTop?: number,
innerRef?: any,
innerElementType?: string | React$AbstractComponent<InnerProps, any>,
innerTagName?: string, // deprecated
itemData: T,
itemKey?: (params: {|
columnIndex: number,
data: T,
rowIndex: number,
|}) => any,
onItemsRendered?: OnItemsRenderedCallback,
onScroll?: OnScrollCallback,
outerRef?: any,
outerElementType?: string | React$AbstractComponent<OuterProps, any>,
outerTagName?: string, // deprecated
overscanColumnCount?: number,
overscanColumnsCount?: number, // deprecated
overscanCount?: number, // deprecated
overscanRowCount?: number,
overscanRowsCount?: number, // deprecated
rowCount: number,
rowHeight: itemSize,
style?: Object,
useIsScrolling: boolean,
width: number,
|};
type State = {|
instance: any,
isScrolling: boolean,
horizontalScrollDirection: ScrollDirection,
scrollLeft: number,
scrollTop: number,
scrollUpdateWasRequested: boolean,
verticalScrollDirection: ScrollDirection,
|};
type getItemOffset = (
props: Props<any>,
index: number,
instanceProps: any
) => number;
type getItemSize = (
props: Props<any>,
index: number,
instanceProps: any
) => number;
type getEstimatedTotalSize = (props: Props<any>, instanceProps: any) => number;
type GetOffsetForItemAndAlignment = (
props: Props<any>,
index: number,
align: ScrollToAlign,
scrollOffset: number,
instanceProps: any,
scrollbarSize: number
) => number;
type GetStartIndexForOffset = (
props: Props<any>,
offset: number,
instanceProps: any
) => number;
type GetStopIndexForStartIndex = (
props: Props<any>,
startIndex: number,
scrollOffset: number,
instanceProps: any
) => number;
type InitInstanceProps = (props: Props<any>, instance: any) => any;
type ValidateProps = (props: Props<any>) => void;
const IS_SCROLLING_DEBOUNCE_INTERVAL = 150;
const defaultItemKey = ({ columnIndex, data, rowIndex }) =>
`${rowIndex}:${columnIndex}`;
// In DEV mode, this Set helps us only log a warning once per component instance.
// This avoids spamming the console every time a render happens.
let devWarningsOverscanCount = null;
let devWarningsOverscanRowsColumnsCount = null;
let devWarningsTagName = null;
if (process.env.NODE_ENV !== 'production') {
if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') {
devWarningsOverscanCount = new WeakSet();
devWarningsOverscanRowsColumnsCount = new WeakSet();
devWarningsTagName = new WeakSet();
}
}
export default function createGridComponent({
getColumnOffset,
getColumnStartIndexForOffset,
getColumnStopIndexForStartIndex,
getColumnWidth,
getEstimatedTotalHeight,
getEstimatedTotalWidth,
getOffsetForColumnAndAlignment,
getOffsetForRowAndAlignment,
getRowHeight,
getRowOffset,
getRowStartIndexForOffset,
getRowStopIndexForStartIndex,
initInstanceProps,
shouldResetStyleCacheOnItemSizeChange,
validateProps,
}: {|
getColumnOffset: getItemOffset,
getColumnStartIndexForOffset: GetStartIndexForOffset,
getColumnStopIndexForStartIndex: GetStopIndexForStartIndex,
getColumnWidth: getItemSize,
getEstimatedTotalHeight: getEstimatedTotalSize,
getEstimatedTotalWidth: getEstimatedTotalSize,
getOffsetForColumnAndAlignment: GetOffsetForItemAndAlignment,
getOffsetForRowAndAlignment: GetOffsetForItemAndAlignment,
getRowOffset: getItemOffset,
getRowHeight: getItemSize,
getRowStartIndexForOffset: GetStartIndexForOffset,
getRowStopIndexForStartIndex: GetStopIndexForStartIndex,
initInstanceProps: InitInstanceProps,
shouldResetStyleCacheOnItemSizeChange: boolean,
validateProps: ValidateProps,
|}) {
return class Grid<T> extends PureComponent<Props<T>, State> {
_instanceProps: any = initInstanceProps(this.props, this);
_resetIsScrollingTimeoutId: TimeoutID | null = null;
_outerRef: ?HTMLDivElement;
static defaultProps = {
direction: 'ltr',
itemData: undefined,
useIsScrolling: false,
};
state: State = {
instance: this,
isScrolling: false,
horizontalScrollDirection: 'forward',
scrollLeft:
typeof this.props.initialScrollLeft === 'number'
? this.props.initialScrollLeft
: 0,
scrollTop:
typeof this.props.initialScrollTop === 'number'
? this.props.initialScrollTop
: 0,
scrollUpdateWasRequested: false,
verticalScrollDirection: 'forward',
};
// Always use explicit constructor for React components.
// It produces less code after transpilation. (#26)
// eslint-disable-next-line no-useless-constructor
constructor(props: Props<T>) {
super(props);
}
static getDerivedStateFromProps(
nextProps: Props<T>,
prevState: State
): $Shape<State> | null {
validateSharedProps(nextProps, prevState);
validateProps(nextProps);
return null;
}
scrollTo({
scrollLeft,
scrollTop,
}: {
scrollLeft: number,
scrollTop: number,
}): void {
if (scrollLeft !== undefined) {
scrollLeft = Math.max(0, scrollLeft);
}
if (scrollTop !== undefined) {
scrollTop = Math.max(0, scrollTop);
}
this.setState(prevState => {
if (scrollLeft === undefined) {
scrollLeft = prevState.scrollLeft;
}
if (scrollTop === undefined) {
scrollTop = prevState.scrollTop;
}
if (
prevState.scrollLeft === scrollLeft &&
prevState.scrollTop === scrollTop
) {
return null;
}
return {
horizontalScrollDirection:
prevState.scrollLeft < scrollLeft ? 'forward' : 'backward',
scrollLeft: scrollLeft,
scrollTop: scrollTop,
scrollUpdateWasRequested: true,
verticalScrollDirection:
prevState.scrollTop < scrollTop ? 'forward' : 'backward',
};
}, this._resetIsScrollingDebounced);
}
scrollToItem({
align = 'auto',
columnIndex,
rowIndex,
}: {
align: ScrollToAlign,
columnIndex?: number,
rowIndex?: number,
}): void {
const { columnCount, height, rowCount, width } = this.props;
const { scrollLeft, scrollTop } = this.state;
const scrollbarSize = getScrollbarSize();
if (columnIndex !== undefined) {
columnIndex = Math.max(0, Math.min(columnIndex, columnCount - 1));
}
if (rowIndex !== undefined) {
rowIndex = Math.max(0, Math.min(rowIndex, rowCount - 1));
}
const estimatedTotalHeight = getEstimatedTotalHeight(
this.props,
this._instanceProps
);
const estimatedTotalWidth = getEstimatedTotalWidth(
this.props,
this._instanceProps
);
// The scrollbar size should be considered when scrolling an item into view,
// to ensure it's fully visible.
// But we only need to account for its size when it's actually visible.
const horizontalScrollbarSize =
estimatedTotalWidth > width ? scrollbarSize : 0;
const verticalScrollbarSize =
estimatedTotalHeight > height ? scrollbarSize : 0;
this.scrollTo({
scrollLeft:
columnIndex !== undefined
? getOffsetForColumnAndAlignment(
this.props,
columnIndex,
align,
scrollLeft,
this._instanceProps,
verticalScrollbarSize
)
: scrollLeft,
scrollTop:
rowIndex !== undefined
? getOffsetForRowAndAlignment(
this.props,
rowIndex,
align,
scrollTop,
this._instanceProps,
horizontalScrollbarSize
)
: scrollTop,
});
}
componentDidMount() {
const { initialScrollLeft, initialScrollTop } = this.props;
if (this._outerRef != null) {
const outerRef = ((this._outerRef: any): HTMLElement);
if (typeof initialScrollLeft === 'number') {
outerRef.scrollLeft = initialScrollLeft;
}
if (typeof initialScrollTop === 'number') {
outerRef.scrollTop = initialScrollTop;
}
}
this._callPropsCallbacks();
}
componentDidUpdate() {
const { direction } = this.props;
const { scrollLeft, scrollTop, scrollUpdateWasRequested } = this.state;
if (scrollUpdateWasRequested && this._outerRef != null) {
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
// This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
// So we need to determine which browser behavior we're dealing with, and mimic it.
const outerRef = ((this._outerRef: any): HTMLElement);
if (direction === 'rtl') {
switch (getRTLOffsetType()) {
case 'negative':
outerRef.scrollLeft = -scrollLeft;
break;
case 'positive-ascending':
outerRef.scrollLeft = scrollLeft;
break;
default:
const { clientWidth, scrollWidth } = outerRef;
outerRef.scrollLeft = scrollWidth - clientWidth - scrollLeft;
break;
}
} else {
outerRef.scrollLeft = Math.max(0, scrollLeft);
}
outerRef.scrollTop = Math.max(0, scrollTop);
}
this._callPropsCallbacks();
}
componentWillUnmount() {
if (this._resetIsScrollingTimeoutId !== null) {
cancelTimeout(this._resetIsScrollingTimeoutId);
}
}
render() {
const {
children,
className,
columnCount,
direction,
height,
innerRef,
innerElementType,
innerTagName,
itemData,
itemKey = defaultItemKey,
outerElementType,
outerTagName,
rowCount,
style,
useIsScrolling,
width,
} = this.props;
const { isScrolling } = this.state;
const [
columnStartIndex,
columnStopIndex,
] = this._getHorizontalRangeToRender();
const [rowStartIndex, rowStopIndex] = this._getVerticalRangeToRender();
const items = [];
if (columnCount > 0 && rowCount) {
for (
let rowIndex = rowStartIndex;
rowIndex <= rowStopIndex;
rowIndex++
) {
for (
let columnIndex = columnStartIndex;
columnIndex <= columnStopIndex;
columnIndex++
) {
items.push(
createElement(children, {
columnIndex,
data: itemData,
isScrolling: useIsScrolling ? isScrolling : undefined,
key: itemKey({ columnIndex, data: itemData, rowIndex }),
rowIndex,
style: this._getItemStyle(rowIndex, columnIndex),
})
);
}
}
}
// Read this value AFTER items have been created,
// So their actual sizes (if variable) are taken into consideration.
const estimatedTotalHeight = getEstimatedTotalHeight(
this.props,
this._instanceProps
);
const estimatedTotalWidth = getEstimatedTotalWidth(
this.props,
this._instanceProps
);
return createElement(
outerElementType || outerTagName || 'div',
{
className,
onScroll: this._onScroll,
ref: this._outerRefSetter,
style: {
position: 'relative',
height,
width,
overflow: 'auto',
WebkitOverflowScrolling: 'touch',
willChange: 'transform',
direction,
...style,
},
},
createElement(innerElementType || innerTagName || 'div', {
children: items,
ref: innerRef,
style: {
height: estimatedTotalHeight,
pointerEvents: isScrolling ? 'none' : undefined,
width: estimatedTotalWidth,
},
})
);
}
_callOnItemsRendered: (
overscanColumnStartIndex: number,
overscanColumnStopIndex: number,
overscanRowStartIndex: number,
overscanRowStopIndex: number,
visibleColumnStartIndex: number,
visibleColumnStopIndex: number,
visibleRowStartIndex: number,
visibleRowStopIndex: number
) => void;
_callOnItemsRendered = memoizeOne(
(
overscanColumnStartIndex: number,
overscanColumnStopIndex: number,
overscanRowStartIndex: number,
overscanRowStopIndex: number,
visibleColumnStartIndex: number,
visibleColumnStopIndex: number,
visibleRowStartIndex: number,
visibleRowStopIndex: number
) =>
((this.props.onItemsRendered: any): OnItemsRenderedCallback)({
overscanColumnStartIndex,
overscanColumnStopIndex,
overscanRowStartIndex,
overscanRowStopIndex,
visibleColumnStartIndex,
visibleColumnStopIndex,
visibleRowStartIndex,
visibleRowStopIndex,
})
);
_callOnScroll: (
scrollLeft: number,
scrollTop: number,
horizontalScrollDirection: ScrollDirection,
verticalScrollDirection: ScrollDirection,
scrollUpdateWasRequested: boolean
) => void;
_callOnScroll = memoizeOne(
(
scrollLeft: number,
scrollTop: number,
horizontalScrollDirection: ScrollDirection,
verticalScrollDirection: ScrollDirection,
scrollUpdateWasRequested: boolean
) =>
((this.props.onScroll: any): OnScrollCallback)({
horizontalScrollDirection,
scrollLeft,
scrollTop,
verticalScrollDirection,
scrollUpdateWasRequested,
})
);
_callPropsCallbacks() {
const { columnCount, onItemsRendered, onScroll, rowCount } = this.props;
if (typeof onItemsRendered === 'function') {
if (columnCount > 0 && rowCount > 0) {
const [
overscanColumnStartIndex,
overscanColumnStopIndex,
visibleColumnStartIndex,
visibleColumnStopIndex,
] = this._getHorizontalRangeToRender();
const [
overscanRowStartIndex,
overscanRowStopIndex,
visibleRowStartIndex,
visibleRowStopIndex,
] = this._getVerticalRangeToRender();
this._callOnItemsRendered(
overscanColumnStartIndex,
overscanColumnStopIndex,
overscanRowStartIndex,
overscanRowStopIndex,
visibleColumnStartIndex,
visibleColumnStopIndex,
visibleRowStartIndex,
visibleRowStopIndex
);
}
}
if (typeof onScroll === 'function') {
const {
horizontalScrollDirection,
scrollLeft,
scrollTop,
scrollUpdateWasRequested,
verticalScrollDirection,
} = this.state;
this._callOnScroll(
scrollLeft,
scrollTop,
horizontalScrollDirection,
verticalScrollDirection,
scrollUpdateWasRequested
);
}
}
// Lazily create and cache item styles while scrolling,
// So that pure component sCU will prevent re-renders.
// We maintain this cache, and pass a style prop rather than index,
// So that List can clear cached styles and force item re-render if necessary.
_getItemStyle: (rowIndex: number, columnIndex: number) => Object;
_getItemStyle = (rowIndex: number, columnIndex: number): Object => {
const { columnWidth, direction, rowHeight } = this.props;
const itemStyleCache = this._getItemStyleCache(
shouldResetStyleCacheOnItemSizeChange && columnWidth,
shouldResetStyleCacheOnItemSizeChange && direction,
shouldResetStyleCacheOnItemSizeChange && rowHeight
);
const key = `${rowIndex}:${columnIndex}`;
let style;
if (itemStyleCache.hasOwnProperty(key)) {
style = itemStyleCache[key];
} else {
const offset = getColumnOffset(
this.props,
columnIndex,
this._instanceProps
);
const isRtl = direction === 'rtl';
itemStyleCache[key] = style = {
position: 'absolute',
left: isRtl ? undefined : offset,
right: isRtl ? offset : undefined,
top: getRowOffset(this.props, rowIndex, this._instanceProps),
height: getRowHeight(this.props, rowIndex, this._instanceProps),
width: getColumnWidth(this.props, columnIndex, this._instanceProps),
};
}
return style;
};
_getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache;
_getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({}));
_getHorizontalRangeToRender(): [number, number, number, number] {
const {
columnCount,
overscanColumnCount,
overscanColumnsCount,
overscanCount,
rowCount,
} = this.props;
const { horizontalScrollDirection, isScrolling, scrollLeft } = this.state;
const overscanCountResolved: number =
overscanColumnCount || overscanColumnsCount || overscanCount || 1;
if (columnCount === 0 || rowCount === 0) {
return [0, 0, 0, 0];
}
const startIndex = getColumnStartIndexForOffset(
this.props,
scrollLeft,
this._instanceProps
);
const stopIndex = getColumnStopIndexForStartIndex(
this.props,
startIndex,
scrollLeft,
this._instanceProps
);
// Overscan by one item in each direction so that tab/focus works.
// If there isn't at least one extra item, tab loops back around.
const overscanBackward =
!isScrolling || horizontalScrollDirection === 'backward'
? Math.max(1, overscanCountResolved)
: 1;
const overscanForward =
!isScrolling || horizontalScrollDirection === 'forward'
? Math.max(1, overscanCountResolved)
: 1;
return [
Math.max(0, startIndex - overscanBackward),
Math.max(0, Math.min(columnCount - 1, stopIndex + overscanForward)),
startIndex,
stopIndex,
];
}
_getVerticalRangeToRender(): [number, number, number, number] {
const {
columnCount,
overscanCount,
overscanRowCount,
overscanRowsCount,
rowCount,
} = this.props;
const { isScrolling, verticalScrollDirection, scrollTop } = this.state;
const overscanCountResolved: number =
overscanRowCount || overscanRowsCount || overscanCount || 1;
if (columnCount === 0 || rowCount === 0) {
return [0, 0, 0, 0];
}
const startIndex = getRowStartIndexForOffset(
this.props,
scrollTop,
this._instanceProps
);
const stopIndex = getRowStopIndexForStartIndex(
this.props,
startIndex,
scrollTop,
this._instanceProps
);
// Overscan by one item in each direction so that tab/focus works.
// If there isn't at least one extra item, tab loops back around.
const overscanBackward =
!isScrolling || verticalScrollDirection === 'backward'
? Math.max(1, overscanCountResolved)
: 1;
const overscanForward =
!isScrolling || verticalScrollDirection === 'forward'
? Math.max(1, overscanCountResolved)
: 1;
return [
Math.max(0, startIndex - overscanBackward),
Math.max(0, Math.min(rowCount - 1, stopIndex + overscanForward)),
startIndex,
stopIndex,
];
}
_onScroll = (event: ScrollEvent): void => {
const {
clientHeight,
clientWidth,
scrollLeft,
scrollTop,
scrollHeight,
scrollWidth,
} = event.currentTarget;
this.setState(prevState => {
if (
prevState.scrollLeft === scrollLeft &&
prevState.scrollTop === scrollTop
) {
// Scroll position may have been updated by cDM/cDU,
// In which case we don't need to trigger another render,
// And we don't want to update state.isScrolling.
return null;
}
const { direction } = this.props;
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
// This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
// It's also easier for this component if we convert offsets to the same format as they would be in for ltr.
// So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it.
let calculatedScrollLeft = scrollLeft;
if (direction === 'rtl') {
switch (getRTLOffsetType()) {
case 'negative':
calculatedScrollLeft = -scrollLeft;
break;
case 'positive-descending':
calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft;
break;
}
}
// Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds.
calculatedScrollLeft = Math.max(
0,
Math.min(calculatedScrollLeft, scrollWidth - clientWidth)
);
const calculatedScrollTop = Math.max(
0,
Math.min(scrollTop, scrollHeight - clientHeight)
);
return {
isScrolling: true,
horizontalScrollDirection:
prevState.scrollLeft < scrollLeft ? 'forward' : 'backward',
scrollLeft: calculatedScrollLeft,
scrollTop: calculatedScrollTop,
verticalScrollDirection:
prevState.scrollTop < scrollTop ? 'forward' : 'backward',
scrollUpdateWasRequested: false,
};
}, this._resetIsScrollingDebounced);
};
_outerRefSetter = (ref: any): void => {
const { outerRef } = this.props;
this._outerRef = ((ref: any): HTMLDivElement);
if (typeof outerRef === 'function') {
outerRef(ref);
} else if (
outerRef != null &&
typeof outerRef === 'object' &&
outerRef.hasOwnProperty('current')
) {
outerRef.current = ref;
}
};
_resetIsScrollingDebounced = () => {
if (this._resetIsScrollingTimeoutId !== null) {
cancelTimeout(this._resetIsScrollingTimeoutId);
}
this._resetIsScrollingTimeoutId = requestTimeout(
this._resetIsScrolling,
IS_SCROLLING_DEBOUNCE_INTERVAL
);
};
_resetIsScrolling = () => {
this._resetIsScrollingTimeoutId = null;
this.setState({ isScrolling: false }, () => {
// Clear style cache after state update has been committed.
// This way we don't break pure sCU for items that don't use isScrolling param.
this._getItemStyleCache(-1);
});
};
};
}
const validateSharedProps = (
{
children,
direction,
height,
innerTagName,
outerTagName,
overscanColumnsCount,
overscanCount,
overscanRowsCount,
width,
}: Props<any>,
{ instance }: State
): void => {
if (process.env.NODE_ENV !== 'production') {
if (typeof overscanCount === 'number') {
if (devWarningsOverscanCount && !devWarningsOverscanCount.has(instance)) {
devWarningsOverscanCount.add(instance);
console.warn(
'The overscanCount prop has been deprecated. ' +
'Please use the overscanColumnCount and overscanRowCount props instead.'
);
}
}
if (
typeof overscanColumnsCount === 'number' ||
typeof overscanRowsCount === 'number'
) {
if (
devWarningsOverscanRowsColumnsCount &&
!devWarningsOverscanRowsColumnsCount.has(instance)
) {
devWarningsOverscanRowsColumnsCount.add(instance);
console.warn(
'The overscanColumnsCount and overscanRowsCount props have been deprecated. ' +
'Please use the overscanColumnCount and overscanRowCount props instead.'
);
}
}
if (innerTagName != null || outerTagName != null) {
if (devWarningsTagName && !devWarningsTagName.has(instance)) {
devWarningsTagName.add(instance);
console.warn(
'The innerTagName and outerTagName props have been deprecated. ' +
'Please use the innerElementType and outerElementType props instead.'
);
}
}
if (children == null) {
throw Error(
'An invalid "children" prop has been specified. ' +
'Value should be a React component. ' +
`"${children === null ? 'null' : typeof children}" was specified.`
);
}
switch (direction) {
case 'ltr':
case 'rtl':
// Valid values
break;
default:
throw Error(
'An invalid "direction" prop has been specified. ' +
'Value should be either "ltr" or "rtl". ' +
`"${direction}" was specified.`
);
}
if (typeof width !== 'number') {
throw Error(
'An invalid "width" prop has been specified. ' +
'Grids must specify a number for width. ' +
`"${width === null ? 'null' : typeof width}" was specified.`
);
}
if (typeof height !== 'number') {
throw Error(
'An invalid "height" prop has been specified. ' +
'Grids must specify a number for height. ' +
`"${height === null ? 'null' : typeof height}" was specified.`
);
}
}
};

745
node_modules/react-window/src/createListComponent.js generated vendored Normal file
View File

@@ -0,0 +1,745 @@
// @flow
import memoizeOne from 'memoize-one';
import { createElement, PureComponent } from 'react';
import { cancelTimeout, requestTimeout } from './timer';
import { getScrollbarSize, getRTLOffsetType } from './domHelpers';
import type { TimeoutID } from './timer';
export type ScrollToAlign = 'auto' | 'smart' | 'center' | 'start' | 'end';
type itemSize = number | ((index: number) => number);
// TODO Deprecate directions "horizontal" and "vertical"
type Direction = 'ltr' | 'rtl' | 'horizontal' | 'vertical';
type Layout = 'horizontal' | 'vertical';
type RenderComponentProps<T> = {|
data: T,
index: number,
isScrolling?: boolean,
style: Object,
|};
type RenderComponent<T> = React$ComponentType<$Shape<RenderComponentProps<T>>>;
type ScrollDirection = 'forward' | 'backward';
type onItemsRenderedCallback = ({
overscanStartIndex: number,
overscanStopIndex: number,
visibleStartIndex: number,
visibleStopIndex: number,
}) => void;
type onScrollCallback = ({
scrollDirection: ScrollDirection,
scrollOffset: number,
scrollUpdateWasRequested: boolean,
}) => void;
type ScrollEvent = SyntheticEvent<HTMLDivElement>;
type ItemStyleCache = { [index: number]: Object };
type OuterProps = {|
children: React$Node,
className: string | void,
onScroll: ScrollEvent => void,
style: {
[string]: mixed,
},
|};
type InnerProps = {|
children: React$Node,
style: {
[string]: mixed,
},
|};
export type Props<T> = {|
children: RenderComponent<T>,
className?: string,
direction: Direction,
height: number | string,
initialScrollOffset?: number,
innerRef?: any,
innerElementType?: string | React$AbstractComponent<InnerProps, any>,
innerTagName?: string, // deprecated
itemCount: number,
itemData: T,
itemKey?: (index: number, data: T) => any,
itemSize: itemSize,
layout: Layout,
onItemsRendered?: onItemsRenderedCallback,
onScroll?: onScrollCallback,
outerRef?: any,
outerElementType?: string | React$AbstractComponent<OuterProps, any>,
outerTagName?: string, // deprecated
overscanCount: number,
style?: Object,
useIsScrolling: boolean,
width: number | string,
|};
type State = {|
instance: any,
isScrolling: boolean,
scrollDirection: ScrollDirection,
scrollOffset: number,
scrollUpdateWasRequested: boolean,
|};
type GetItemOffset = (
props: Props<any>,
index: number,
instanceProps: any
) => number;
type GetItemSize = (
props: Props<any>,
index: number,
instanceProps: any
) => number;
type GetEstimatedTotalSize = (props: Props<any>, instanceProps: any) => number;
type GetOffsetForIndexAndAlignment = (
props: Props<any>,
index: number,
align: ScrollToAlign,
scrollOffset: number,
instanceProps: any
) => number;
type GetStartIndexForOffset = (
props: Props<any>,
offset: number,
instanceProps: any
) => number;
type GetStopIndexForStartIndex = (
props: Props<any>,
startIndex: number,
scrollOffset: number,
instanceProps: any
) => number;
type InitInstanceProps = (props: Props<any>, instance: any) => any;
type ValidateProps = (props: Props<any>) => void;
const IS_SCROLLING_DEBOUNCE_INTERVAL = 150;
const defaultItemKey = (index: number, data: any) => index;
// In DEV mode, this Set helps us only log a warning once per component instance.
// This avoids spamming the console every time a render happens.
let devWarningsDirection = null;
let devWarningsTagName = null;
if (process.env.NODE_ENV !== 'production') {
if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') {
devWarningsDirection = new WeakSet();
devWarningsTagName = new WeakSet();
}
}
export default function createListComponent({
getItemOffset,
getEstimatedTotalSize,
getItemSize,
getOffsetForIndexAndAlignment,
getStartIndexForOffset,
getStopIndexForStartIndex,
initInstanceProps,
shouldResetStyleCacheOnItemSizeChange,
validateProps,
}: {|
getItemOffset: GetItemOffset,
getEstimatedTotalSize: GetEstimatedTotalSize,
getItemSize: GetItemSize,
getOffsetForIndexAndAlignment: GetOffsetForIndexAndAlignment,
getStartIndexForOffset: GetStartIndexForOffset,
getStopIndexForStartIndex: GetStopIndexForStartIndex,
initInstanceProps: InitInstanceProps,
shouldResetStyleCacheOnItemSizeChange: boolean,
validateProps: ValidateProps,
|}) {
return class List<T> extends PureComponent<Props<T>, State> {
_instanceProps: any = initInstanceProps(this.props, this);
_outerRef: ?HTMLDivElement;
_resetIsScrollingTimeoutId: TimeoutID | null = null;
static defaultProps = {
direction: 'ltr',
itemData: undefined,
layout: 'vertical',
overscanCount: 2,
useIsScrolling: false,
};
state: State = {
instance: this,
isScrolling: false,
scrollDirection: 'forward',
scrollOffset:
typeof this.props.initialScrollOffset === 'number'
? this.props.initialScrollOffset
: 0,
scrollUpdateWasRequested: false,
};
// Always use explicit constructor for React components.
// It produces less code after transpilation. (#26)
// eslint-disable-next-line no-useless-constructor
constructor(props: Props<T>) {
super(props);
}
static getDerivedStateFromProps(
nextProps: Props<T>,
prevState: State
): $Shape<State> | null {
validateSharedProps(nextProps, prevState);
validateProps(nextProps);
return null;
}
scrollTo(scrollOffset: number): void {
scrollOffset = Math.max(0, scrollOffset);
this.setState(prevState => {
if (prevState.scrollOffset === scrollOffset) {
return null;
}
return {
scrollDirection:
prevState.scrollOffset < scrollOffset ? 'forward' : 'backward',
scrollOffset: scrollOffset,
scrollUpdateWasRequested: true,
};
}, this._resetIsScrollingDebounced);
}
scrollToItem(index: number, align: ScrollToAlign = 'auto'): void {
const { itemCount, layout } = this.props;
const { scrollOffset } = this.state;
index = Math.max(0, Math.min(index, itemCount - 1));
// The scrollbar size should be considered when scrolling an item into view, to ensure it's fully visible.
// But we only need to account for its size when it's actually visible.
// This is an edge case for lists; normally they only scroll in the dominant direction.
let scrollbarSize = 0;
if (this._outerRef) {
const outerRef = ((this._outerRef: any): HTMLElement);
if (layout === 'vertical') {
scrollbarSize =
outerRef.scrollWidth > outerRef.clientWidth
? getScrollbarSize()
: 0;
} else {
scrollbarSize =
outerRef.scrollHeight > outerRef.clientHeight
? getScrollbarSize()
: 0;
}
}
this.scrollTo(
getOffsetForIndexAndAlignment(
this.props,
index,
align,
scrollOffset,
this._instanceProps,
scrollbarSize
)
);
}
componentDidMount() {
const { direction, initialScrollOffset, layout } = this.props;
if (typeof initialScrollOffset === 'number' && this._outerRef != null) {
const outerRef = ((this._outerRef: any): HTMLElement);
// TODO Deprecate direction "horizontal"
if (direction === 'horizontal' || layout === 'horizontal') {
outerRef.scrollLeft = initialScrollOffset;
} else {
outerRef.scrollTop = initialScrollOffset;
}
}
this._callPropsCallbacks();
}
componentDidUpdate() {
const { direction, layout } = this.props;
const { scrollOffset, scrollUpdateWasRequested } = this.state;
if (scrollUpdateWasRequested && this._outerRef != null) {
const outerRef = ((this._outerRef: any): HTMLElement);
// TODO Deprecate direction "horizontal"
if (direction === 'horizontal' || layout === 'horizontal') {
if (direction === 'rtl') {
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
// This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
// So we need to determine which browser behavior we're dealing with, and mimic it.
switch (getRTLOffsetType()) {
case 'negative':
outerRef.scrollLeft = -scrollOffset;
break;
case 'positive-ascending':
outerRef.scrollLeft = scrollOffset;
break;
default:
const { clientWidth, scrollWidth } = outerRef;
outerRef.scrollLeft = scrollWidth - clientWidth - scrollOffset;
break;
}
} else {
outerRef.scrollLeft = scrollOffset;
}
} else {
outerRef.scrollTop = scrollOffset;
}
}
this._callPropsCallbacks();
}
componentWillUnmount() {
if (this._resetIsScrollingTimeoutId !== null) {
cancelTimeout(this._resetIsScrollingTimeoutId);
}
}
render() {
const {
children,
className,
direction,
height,
innerRef,
innerElementType,
innerTagName,
itemCount,
itemData,
itemKey = defaultItemKey,
layout,
outerElementType,
outerTagName,
style,
useIsScrolling,
width,
} = this.props;
const { isScrolling } = this.state;
// TODO Deprecate direction "horizontal"
const isHorizontal =
direction === 'horizontal' || layout === 'horizontal';
const onScroll = isHorizontal
? this._onScrollHorizontal
: this._onScrollVertical;
const [startIndex, stopIndex] = this._getRangeToRender();
const items = [];
if (itemCount > 0) {
for (let index = startIndex; index <= stopIndex; index++) {
items.push(
createElement(children, {
data: itemData,
key: itemKey(index, itemData),
index,
isScrolling: useIsScrolling ? isScrolling : undefined,
style: this._getItemStyle(index),
})
);
}
}
// Read this value AFTER items have been created,
// So their actual sizes (if variable) are taken into consideration.
const estimatedTotalSize = getEstimatedTotalSize(
this.props,
this._instanceProps
);
return createElement(
outerElementType || outerTagName || 'div',
{
className,
onScroll,
ref: this._outerRefSetter,
style: {
position: 'relative',
height,
width,
overflow: 'auto',
WebkitOverflowScrolling: 'touch',
willChange: 'transform',
direction,
...style,
},
},
createElement(innerElementType || innerTagName || 'div', {
children: items,
ref: innerRef,
style: {
height: isHorizontal ? '100%' : estimatedTotalSize,
pointerEvents: isScrolling ? 'none' : undefined,
width: isHorizontal ? estimatedTotalSize : '100%',
},
})
);
}
_callOnItemsRendered: (
overscanStartIndex: number,
overscanStopIndex: number,
visibleStartIndex: number,
visibleStopIndex: number
) => void;
_callOnItemsRendered = memoizeOne(
(
overscanStartIndex: number,
overscanStopIndex: number,
visibleStartIndex: number,
visibleStopIndex: number
) =>
((this.props.onItemsRendered: any): onItemsRenderedCallback)({
overscanStartIndex,
overscanStopIndex,
visibleStartIndex,
visibleStopIndex,
})
);
_callOnScroll: (
scrollDirection: ScrollDirection,
scrollOffset: number,
scrollUpdateWasRequested: boolean
) => void;
_callOnScroll = memoizeOne(
(
scrollDirection: ScrollDirection,
scrollOffset: number,
scrollUpdateWasRequested: boolean
) =>
((this.props.onScroll: any): onScrollCallback)({
scrollDirection,
scrollOffset,
scrollUpdateWasRequested,
})
);
_callPropsCallbacks() {
if (typeof this.props.onItemsRendered === 'function') {
const { itemCount } = this.props;
if (itemCount > 0) {
const [
overscanStartIndex,
overscanStopIndex,
visibleStartIndex,
visibleStopIndex,
] = this._getRangeToRender();
this._callOnItemsRendered(
overscanStartIndex,
overscanStopIndex,
visibleStartIndex,
visibleStopIndex
);
}
}
if (typeof this.props.onScroll === 'function') {
const {
scrollDirection,
scrollOffset,
scrollUpdateWasRequested,
} = this.state;
this._callOnScroll(
scrollDirection,
scrollOffset,
scrollUpdateWasRequested
);
}
}
// Lazily create and cache item styles while scrolling,
// So that pure component sCU will prevent re-renders.
// We maintain this cache, and pass a style prop rather than index,
// So that List can clear cached styles and force item re-render if necessary.
_getItemStyle: (index: number) => Object;
_getItemStyle = (index: number): Object => {
const { direction, itemSize, layout } = this.props;
const itemStyleCache = this._getItemStyleCache(
shouldResetStyleCacheOnItemSizeChange && itemSize,
shouldResetStyleCacheOnItemSizeChange && layout,
shouldResetStyleCacheOnItemSizeChange && direction
);
let style;
if (itemStyleCache.hasOwnProperty(index)) {
style = itemStyleCache[index];
} else {
const offset = getItemOffset(this.props, index, this._instanceProps);
const size = getItemSize(this.props, index, this._instanceProps);
// TODO Deprecate direction "horizontal"
const isHorizontal =
direction === 'horizontal' || layout === 'horizontal';
const isRtl = direction === 'rtl';
const offsetHorizontal = isHorizontal ? offset : 0;
itemStyleCache[index] = style = {
position: 'absolute',
left: isRtl ? undefined : offsetHorizontal,
right: isRtl ? offsetHorizontal : undefined,
top: !isHorizontal ? offset : 0,
height: !isHorizontal ? size : '100%',
width: isHorizontal ? size : '100%',
};
}
return style;
};
_getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache;
_getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({}));
_getRangeToRender(): [number, number, number, number] {
const { itemCount, overscanCount } = this.props;
const { isScrolling, scrollDirection, scrollOffset } = this.state;
if (itemCount === 0) {
return [0, 0, 0, 0];
}
const startIndex = getStartIndexForOffset(
this.props,
scrollOffset,
this._instanceProps
);
const stopIndex = getStopIndexForStartIndex(
this.props,
startIndex,
scrollOffset,
this._instanceProps
);
// Overscan by one item in each direction so that tab/focus works.
// If there isn't at least one extra item, tab loops back around.
const overscanBackward =
!isScrolling || scrollDirection === 'backward'
? Math.max(1, overscanCount)
: 1;
const overscanForward =
!isScrolling || scrollDirection === 'forward'
? Math.max(1, overscanCount)
: 1;
return [
Math.max(0, startIndex - overscanBackward),
Math.max(0, Math.min(itemCount - 1, stopIndex + overscanForward)),
startIndex,
stopIndex,
];
}
_onScrollHorizontal = (event: ScrollEvent): void => {
const { clientWidth, scrollLeft, scrollWidth } = event.currentTarget;
this.setState(prevState => {
if (prevState.scrollOffset === scrollLeft) {
// Scroll position may have been updated by cDM/cDU,
// In which case we don't need to trigger another render,
// And we don't want to update state.isScrolling.
return null;
}
const { direction } = this.props;
let scrollOffset = scrollLeft;
if (direction === 'rtl') {
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
// This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
// It's also easier for this component if we convert offsets to the same format as they would be in for ltr.
// So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it.
switch (getRTLOffsetType()) {
case 'negative':
scrollOffset = -scrollLeft;
break;
case 'positive-descending':
scrollOffset = scrollWidth - clientWidth - scrollLeft;
break;
}
}
// Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds.
scrollOffset = Math.max(
0,
Math.min(scrollOffset, scrollWidth - clientWidth)
);
return {
isScrolling: true,
scrollDirection:
prevState.scrollOffset < scrollOffset ? 'forward' : 'backward',
scrollOffset,
scrollUpdateWasRequested: false,
};
}, this._resetIsScrollingDebounced);
};
_onScrollVertical = (event: ScrollEvent): void => {
const { clientHeight, scrollHeight, scrollTop } = event.currentTarget;
this.setState(prevState => {
if (prevState.scrollOffset === scrollTop) {
// Scroll position may have been updated by cDM/cDU,
// In which case we don't need to trigger another render,
// And we don't want to update state.isScrolling.
return null;
}
// Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds.
const scrollOffset = Math.max(
0,
Math.min(scrollTop, scrollHeight - clientHeight)
);
return {
isScrolling: true,
scrollDirection:
prevState.scrollOffset < scrollOffset ? 'forward' : 'backward',
scrollOffset,
scrollUpdateWasRequested: false,
};
}, this._resetIsScrollingDebounced);
};
_outerRefSetter = (ref: any): void => {
const { outerRef } = this.props;
this._outerRef = ((ref: any): HTMLDivElement);
if (typeof outerRef === 'function') {
outerRef(ref);
} else if (
outerRef != null &&
typeof outerRef === 'object' &&
outerRef.hasOwnProperty('current')
) {
outerRef.current = ref;
}
};
_resetIsScrollingDebounced = () => {
if (this._resetIsScrollingTimeoutId !== null) {
cancelTimeout(this._resetIsScrollingTimeoutId);
}
this._resetIsScrollingTimeoutId = requestTimeout(
this._resetIsScrolling,
IS_SCROLLING_DEBOUNCE_INTERVAL
);
};
_resetIsScrolling = () => {
this._resetIsScrollingTimeoutId = null;
this.setState({ isScrolling: false }, () => {
// Clear style cache after state update has been committed.
// This way we don't break pure sCU for items that don't use isScrolling param.
this._getItemStyleCache(-1, null);
});
};
};
}
// NOTE: I considered further wrapping individual items with a pure ListItem component.
// This would avoid ever calling the render function for the same index more than once,
// But it would also add the overhead of a lot of components/fibers.
// I assume people already do this (render function returning a class component),
// So my doing it would just unnecessarily double the wrappers.
const validateSharedProps = (
{
children,
direction,
height,
layout,
innerTagName,
outerTagName,
width,
}: Props<any>,
{ instance }: State
): void => {
if (process.env.NODE_ENV !== 'production') {
if (innerTagName != null || outerTagName != null) {
if (devWarningsTagName && !devWarningsTagName.has(instance)) {
devWarningsTagName.add(instance);
console.warn(
'The innerTagName and outerTagName props have been deprecated. ' +
'Please use the innerElementType and outerElementType props instead.'
);
}
}
// TODO Deprecate direction "horizontal"
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
switch (direction) {
case 'horizontal':
case 'vertical':
if (devWarningsDirection && !devWarningsDirection.has(instance)) {
devWarningsDirection.add(instance);
console.warn(
'The direction prop should be either "ltr" (default) or "rtl". ' +
'Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.'
);
}
break;
case 'ltr':
case 'rtl':
// Valid values
break;
default:
throw Error(
'An invalid "direction" prop has been specified. ' +
'Value should be either "ltr" or "rtl". ' +
`"${direction}" was specified.`
);
}
switch (layout) {
case 'horizontal':
case 'vertical':
// Valid values
break;
default:
throw Error(
'An invalid "layout" prop has been specified. ' +
'Value should be either "horizontal" or "vertical". ' +
`"${layout}" was specified.`
);
}
if (children == null) {
throw Error(
'An invalid "children" prop has been specified. ' +
'Value should be a React component. ' +
`"${children === null ? 'null' : typeof children}" was specified.`
);
}
if (isHorizontal && typeof width !== 'number') {
throw Error(
'An invalid "width" prop has been specified. ' +
'Horizontal lists must specify a number for width. ' +
`"${width === null ? 'null' : typeof width}" was specified.`
);
} else if (!isHorizontal && typeof height !== 'number') {
throw Error(
'An invalid "height" prop has been specified. ' +
'Vertical lists must specify a number for height. ' +
`"${height === null ? 'null' : typeof height}" was specified.`
);
}
}
};

72
node_modules/react-window/src/domHelpers.js generated vendored Normal file
View File

@@ -0,0 +1,72 @@
// @flow
let size: number = -1;
// This utility copied from "dom-helpers" package.
export function getScrollbarSize(recalculate?: boolean = false): number {
if (size === -1 || recalculate) {
const div = document.createElement('div');
const style = div.style;
style.width = '50px';
style.height = '50px';
style.overflow = 'scroll';
((document.body: any): HTMLBodyElement).appendChild(div);
size = div.offsetWidth - div.clientWidth;
((document.body: any): HTMLBodyElement).removeChild(div);
}
return size;
}
export type RTLOffsetType =
| 'negative'
| 'positive-descending'
| 'positive-ascending';
let cachedRTLResult: RTLOffsetType | null = null;
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left).
// Safari's elastic bounce makes detecting this even more complicated wrt potential false positives.
// The safest way to check this is to intentionally set a negative offset,
// and then verify that the subsequent "scroll" event matches the negative offset.
// If it does not match, then we can assume a non-standard RTL scroll implementation.
export function getRTLOffsetType(recalculate?: boolean = false): RTLOffsetType {
if (cachedRTLResult === null || recalculate) {
const outerDiv = document.createElement('div');
const outerStyle = outerDiv.style;
outerStyle.width = '50px';
outerStyle.height = '50px';
outerStyle.overflow = 'scroll';
outerStyle.direction = 'rtl';
const innerDiv = document.createElement('div');
const innerStyle = innerDiv.style;
innerStyle.width = '100px';
innerStyle.height = '100px';
outerDiv.appendChild(innerDiv);
((document.body: any): HTMLBodyElement).appendChild(outerDiv);
if (outerDiv.scrollLeft > 0) {
cachedRTLResult = 'positive-descending';
} else {
outerDiv.scrollLeft = 1;
if (outerDiv.scrollLeft === 0) {
cachedRTLResult = 'negative';
} else {
cachedRTLResult = 'positive-ascending';
}
}
((document.body: any): HTMLBodyElement).removeChild(outerDiv);
return cachedRTLResult;
}
return cachedRTLResult;
}

9
node_modules/react-window/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,9 @@
// @flow
export { default as VariableSizeGrid } from './VariableSizeGrid';
export { default as VariableSizeList } from './VariableSizeList';
export { default as FixedSizeGrid } from './FixedSizeGrid';
export { default as FixedSizeList } from './FixedSizeList';
export { default as areEqual } from './areEqual';
export { default as shouldComponentUpdate } from './shouldComponentUpdate';

17
node_modules/react-window/src/shallowDiffers.js generated vendored Normal file
View File

@@ -0,0 +1,17 @@
// @flow
// Pulled from react-compat
// https://github.com/developit/preact-compat/blob/7c5de00e7c85e2ffd011bf3af02899b63f699d3a/src/index.js#L349
export default function shallowDiffers(prev: Object, next: Object): boolean {
for (let attribute in prev) {
if (!(attribute in next)) {
return true;
}
}
for (let attribute in next) {
if (prev[attribute] !== next[attribute]) {
return true;
}
}
return false;
}

16
node_modules/react-window/src/shouldComponentUpdate.js generated vendored Normal file
View File

@@ -0,0 +1,16 @@
// @flow
import areEqual from './areEqual';
import shallowDiffers from './shallowDiffers';
// Custom shouldComponentUpdate for class components.
// It knows to compare individual style props and ignore the wrapper object.
// See https://reactjs.org/docs/react-component.html#shouldcomponentupdate
export default function shouldComponentUpdate(
nextProps: Object,
nextState: Object
): boolean {
return (
!areEqual(this.props, nextProps) || shallowDiffers(this.state, nextState)
);
}

37
node_modules/react-window/src/timer.js generated vendored Normal file
View File

@@ -0,0 +1,37 @@
// @flow
// Animation frame based implementation of setTimeout.
// Inspired by Joe Lambert, https://gist.github.com/joelambert/1002116#file-requesttimeout-js
const hasNativePerformanceNow =
typeof performance === 'object' && typeof performance.now === 'function';
const now = hasNativePerformanceNow
? () => performance.now()
: () => Date.now();
export type TimeoutID = {|
id: AnimationFrameID,
|};
export function cancelTimeout(timeoutID: TimeoutID) {
cancelAnimationFrame(timeoutID.id);
}
export function requestTimeout(callback: Function, delay: number): TimeoutID {
const start = now();
function tick() {
if (now() - start >= delay) {
callback.call(null);
} else {
timeoutID.id = requestAnimationFrame(tick);
}
}
const timeoutID: TimeoutID = {
id: requestAnimationFrame(tick),
};
return timeoutID;
}