mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 20:44:32 -05:00
♻️ (typescript) refactor FixedSizeList to TS (#2022)
This commit is contained in:
committed by
GitHub
parent
2741422c0a
commit
7955fe7aed
@@ -1,25 +1,95 @@
|
||||
import React, { createRef, PureComponent } from 'react';
|
||||
import {
|
||||
createRef,
|
||||
PureComponent,
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
type Ref,
|
||||
type MutableRefObject,
|
||||
type UIEvent,
|
||||
} from 'react';
|
||||
|
||||
import memoizeOne from 'memoize-one';
|
||||
|
||||
import useResizeObserver from '../hooks/useResizeObserver';
|
||||
import { type CSSProperties } from '../style';
|
||||
|
||||
import View from './common/View';
|
||||
|
||||
const IS_SCROLLING_DEBOUNCE_INTERVAL = 150;
|
||||
|
||||
const defaultItemKey = (index, data) => index;
|
||||
const defaultItemKey: FixedSizeListProps['itemKey'] = (index: number) => index;
|
||||
|
||||
function ResizeObserver({ onResize, children }) {
|
||||
type ResizeObserverProps = {
|
||||
onResize: Parameters<typeof useResizeObserver>[0];
|
||||
children: (ref: Ref<HTMLDivElement>) => ReactElement;
|
||||
};
|
||||
|
||||
function ResizeObserver({ onResize, children }: ResizeObserverProps) {
|
||||
const ref = useResizeObserver(onResize);
|
||||
return children(ref);
|
||||
}
|
||||
|
||||
export default class FixedSizeList extends PureComponent {
|
||||
_outerRef;
|
||||
_resetIsScrollingTimeoutId = null;
|
||||
type FixedSizeListProps = {
|
||||
className?: string;
|
||||
direction?: 'rtl' | 'ltr';
|
||||
renderRow: (props: {
|
||||
index: number;
|
||||
key: string | number;
|
||||
style: CSSProperties;
|
||||
isScrolling?: boolean;
|
||||
isAnimating: boolean;
|
||||
}) => ReactNode;
|
||||
layout?: 'vertical' | 'horizontal';
|
||||
overscanCount?: number;
|
||||
useIsScrolling?: boolean;
|
||||
headerHeight?: number;
|
||||
initialScrollOffset?: number;
|
||||
itemCount?: number;
|
||||
outerRef?: MutableRefObject<HTMLDivElement>;
|
||||
itemSize?: number;
|
||||
onItemsRendered?: (config: {
|
||||
overscanStartIndex: number;
|
||||
overscanStopIndex: number;
|
||||
visibleStartIndex?: number;
|
||||
visibleStopIndex?: number;
|
||||
}) => void;
|
||||
onScroll?: (config: {
|
||||
scrollDirection: FixedSizeListState['scrollDirection'];
|
||||
scrollOffset: FixedSizeListState['scrollOffset'];
|
||||
scrollUpdateWasRequested: FixedSizeListState['scrollUpdateWasRequested'];
|
||||
}) => void;
|
||||
indexForKey?: (key: string | number) => number;
|
||||
height?: number;
|
||||
width?: number;
|
||||
header?: ReactNode;
|
||||
innerRef?: Ref<HTMLDivElement>;
|
||||
itemKey?: (index: number) => string | number;
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
type FixedSizeListState = {
|
||||
isScrolling: boolean;
|
||||
scrollDirection: 'forward' | 'backward';
|
||||
scrollOffset: number;
|
||||
scrollUpdateWasRequested: boolean;
|
||||
};
|
||||
|
||||
export default class FixedSizeList extends PureComponent<
|
||||
FixedSizeListProps,
|
||||
FixedSizeListState
|
||||
> {
|
||||
_outerRef: HTMLDivElement;
|
||||
_resetIsScrollingTimeoutId = null;
|
||||
lastPositions: MutableRefObject<Map<string | number, number>>;
|
||||
needsAnimationRerender: MutableRefObject<boolean>;
|
||||
animationEnabled: boolean;
|
||||
requestScrollUpdateHandled: boolean;
|
||||
anchored: null | {
|
||||
key: string | number;
|
||||
offset: number;
|
||||
} = null;
|
||||
rerenderTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
static defaultProps: Partial<FixedSizeListProps> = {
|
||||
direction: 'ltr',
|
||||
renderRow: undefined,
|
||||
layout: 'vertical',
|
||||
@@ -28,7 +98,7 @@ export default class FixedSizeList extends PureComponent {
|
||||
headerHeight: 0,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: FixedSizeListProps) {
|
||||
super(props);
|
||||
|
||||
this.lastPositions = createRef();
|
||||
@@ -38,7 +108,6 @@ export default class FixedSizeList extends PureComponent {
|
||||
this.animationEnabled = false;
|
||||
|
||||
this.state = {
|
||||
instance: this,
|
||||
isScrolling: false,
|
||||
scrollDirection: 'forward',
|
||||
scrollOffset:
|
||||
@@ -49,7 +118,7 @@ export default class FixedSizeList extends PureComponent {
|
||||
};
|
||||
}
|
||||
|
||||
scrollTo(scrollOffset) {
|
||||
scrollTo(scrollOffset: number) {
|
||||
scrollOffset = Math.max(0, scrollOffset);
|
||||
|
||||
this.setState(prevState => {
|
||||
@@ -67,7 +136,10 @@ export default class FixedSizeList extends PureComponent {
|
||||
}, this._resetIsScrollingDebounced);
|
||||
}
|
||||
|
||||
scrollToItem(index, align = 'auto') {
|
||||
scrollToItem(
|
||||
index: number,
|
||||
align: 'start' | 'end' | 'smart' | 'auto' | 'center' = 'auto',
|
||||
) {
|
||||
const { itemCount } = this.props;
|
||||
const { scrollOffset } = this.state;
|
||||
|
||||
@@ -138,14 +210,9 @@ export default class FixedSizeList extends PureComponent {
|
||||
height,
|
||||
header,
|
||||
innerRef,
|
||||
innerElementType,
|
||||
innerTagName,
|
||||
itemCount,
|
||||
renderRow,
|
||||
itemKey = defaultItemKey,
|
||||
outerElementType,
|
||||
outerTagName,
|
||||
style,
|
||||
useIsScrolling,
|
||||
width,
|
||||
} = this.props;
|
||||
@@ -193,13 +260,10 @@ export default class FixedSizeList extends PureComponent {
|
||||
// So their actual sizes (if variable) are taken into consideration.
|
||||
const estimatedTotalSize = this.getEstimatedTotalSize();
|
||||
|
||||
const OuterElement = outerElementType || outerTagName || 'div';
|
||||
const InnerElement = innerElementType || innerTagName || 'div';
|
||||
|
||||
return (
|
||||
<ResizeObserver onResize={this.onHeaderResize}>
|
||||
{headerRef => (
|
||||
<OuterElement
|
||||
<div
|
||||
className={className}
|
||||
onScroll={this._onScrollVertical}
|
||||
ref={this._outerRefSetter}
|
||||
@@ -207,11 +271,10 @@ export default class FixedSizeList extends PureComponent {
|
||||
height,
|
||||
width,
|
||||
overflow: 'hidden auto',
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<View innerRef={headerRef}>{header}</View>
|
||||
<InnerElement
|
||||
<div
|
||||
ref={innerRef}
|
||||
style={{
|
||||
position: 'relative',
|
||||
@@ -221,14 +284,14 @@ export default class FixedSizeList extends PureComponent {
|
||||
}}
|
||||
>
|
||||
{items}
|
||||
</InnerElement>
|
||||
</OuterElement>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ResizeObserver>
|
||||
);
|
||||
}
|
||||
|
||||
setRowAnimation = flag => {
|
||||
setRowAnimation = (flag: boolean) => {
|
||||
this.animationEnabled = flag;
|
||||
|
||||
const outerRef = this._outerRef;
|
||||
@@ -241,7 +304,7 @@ export default class FixedSizeList extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
onHeaderResize = rect => {
|
||||
onHeaderResize = (rect: { height: number }) => {
|
||||
// this.setState({ headerHeight: rect.height });
|
||||
};
|
||||
|
||||
@@ -267,11 +330,15 @@ export default class FixedSizeList extends PureComponent {
|
||||
return this.anchored != null;
|
||||
}
|
||||
|
||||
getItemOffset = index => index * this.props.itemSize;
|
||||
getItemSize = index => this.props.itemSize;
|
||||
getItemOffset = (index: number) => index * this.props.itemSize;
|
||||
getItemSize = () => this.props.itemSize;
|
||||
getEstimatedTotalSize = () => this.props.itemSize * this.props.itemCount;
|
||||
|
||||
getOffsetForIndexAndAlignment = (index, align, scrollOffset) => {
|
||||
getOffsetForIndexAndAlignment = (
|
||||
index: number,
|
||||
align: 'start' | 'end' | 'smart' | 'auto' | 'center',
|
||||
scrollOffset?: number,
|
||||
) => {
|
||||
const size = this.props.height;
|
||||
const lastItemOffset = Math.max(
|
||||
0,
|
||||
@@ -325,7 +392,7 @@ export default class FixedSizeList extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
getStartIndexForOffset = offset =>
|
||||
getStartIndexForOffset = (offset: number) =>
|
||||
Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
@@ -334,7 +401,7 @@ export default class FixedSizeList extends PureComponent {
|
||||
),
|
||||
);
|
||||
|
||||
getStopIndexForStartIndex = (startIndex, scrollOffset) => {
|
||||
getStopIndexForStartIndex = (startIndex: number, scrollOffset: number) => {
|
||||
const offset = startIndex * this.props.itemSize;
|
||||
const size = this.props.height;
|
||||
const numVisibleItems = Math.ceil(
|
||||
@@ -407,17 +474,17 @@ export default class FixedSizeList extends PureComponent {
|
||||
// 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 => {
|
||||
_getItemStyle = (index: number) => {
|
||||
const { direction, itemSize, layout } = this.props;
|
||||
|
||||
const itemStyleCache = this._getItemStyleCache(itemSize, layout, direction);
|
||||
|
||||
let style;
|
||||
let style: CSSProperties;
|
||||
if (itemStyleCache.hasOwnProperty(index)) {
|
||||
style = itemStyleCache[index];
|
||||
} else {
|
||||
const offset = this.getItemOffset(index);
|
||||
const size = this.getItemSize(index);
|
||||
const size = this.getItemSize();
|
||||
|
||||
itemStyleCache[index] = style = {
|
||||
position: 'absolute',
|
||||
@@ -473,7 +540,7 @@ export default class FixedSizeList extends PureComponent {
|
||||
];
|
||||
}
|
||||
|
||||
_onScrollVertical = event => {
|
||||
_onScrollVertical = (event: UIEvent<HTMLDivElement>) => {
|
||||
const { scrollTop } = event.currentTarget;
|
||||
|
||||
this.setState(prevState => {
|
||||
@@ -496,14 +563,12 @@ export default class FixedSizeList extends PureComponent {
|
||||
}, this._resetIsScrollingDebounced);
|
||||
};
|
||||
|
||||
_outerRefSetter = ref => {
|
||||
_outerRefSetter = (ref: HTMLDivElement) => {
|
||||
const { outerRef } = this.props;
|
||||
|
||||
this._outerRef = ref;
|
||||
|
||||
if (typeof outerRef === 'function') {
|
||||
outerRef(ref);
|
||||
} else if (
|
||||
if (
|
||||
outerRef != null &&
|
||||
typeof outerRef === 'object' &&
|
||||
outerRef.hasOwnProperty('current')
|
||||
@@ -529,6 +594,7 @@ export default class FixedSizeList extends PureComponent {
|
||||
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.
|
||||
// @ts-expect-error fix me
|
||||
this._getItemStyleCache(-1, null);
|
||||
});
|
||||
};
|
||||
@@ -1137,7 +1137,7 @@ export const Table: <T extends TableItem>(
|
||||
itemCount={count || items.length}
|
||||
itemSize={rowHeight - 1}
|
||||
itemKey={
|
||||
getItemKey || ((index, data) => items[index].id)
|
||||
getItemKey || ((index: number) => items[index].id)
|
||||
}
|
||||
indexForKey={key =>
|
||||
items.findIndex(item => item.id === key)
|
||||
|
||||
6
upcoming-release-notes/2022.md
Normal file
6
upcoming-release-notes/2022.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Refactored `FixedSizeList` to TypeScript
|
||||
Reference in New Issue
Block a user