Skip to content

Commit

Permalink
List: Parameterize generic type for item with any as default (#8465)
Browse files Browse the repository at this point in the history
* Parameterize List item type

* Default List T to any for backwards compat

* Update API file

* Update ListScrollingExample typing

* Fix ToDoTabs typing List.onRenderCell

* Add change file

* Update API file

* Update API file
  • Loading branch information
KevinTCoughlin authored Mar 26, 2019
1 parent ae94a3c commit 5ad8b88
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 68 deletions.
7 changes: 5 additions & 2 deletions apps/todo-app/src/components/TodoTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ export default class TodoTabs extends React.Component<ITodoTabsProps, {}> {
return ev.which === KeyCodes.right;
};

private _onRenderTodoItem(item: ITodoItem): React.ReactElement<ITodoItemProps> {
return <TodoItem key={item.id} item={item} onToggleComplete={this.props.onToggleComplete} onDeleteItem={this.props.onDeleteItem} />;
private _onRenderTodoItem(item?: ITodoItem): React.ReactElement<ITodoItemProps> | null {
if (item) {
return <TodoItem key={item.id} item={item} onToggleComplete={this.props.onToggleComplete} onDeleteItem={this.props.onDeleteItem} />;
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "office-ui-fabric-react",
"comment": "List: Parameterize generic T for item type with default type any",
"type": "minor"
}
],
"packageName": "office-ui-fabric-react",
"email": "[email protected]"
}
40 changes: 20 additions & 20 deletions packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4930,21 +4930,21 @@ export interface IList {
}

// @public (undocumented)
export interface IListProps extends React.HTMLAttributes<List | HTMLDivElement> {
export interface IListProps<T = any> extends React.HTMLAttributes<List<T> | HTMLDivElement> {
className?: string;
componentRef?: IRefObject<IList>;
getItemCountForPage?: (itemIndex?: number, visibleRect?: IRectangle) => number;
getKey?: (item: any, index?: number) => string;
getKey?: (item: T, index?: number) => string;
getPageHeight?: (itemIndex?: number, visibleRect?: IRectangle) => number;
getPageSpecification?: (itemIndex?: number, visibleRect?: IRectangle) => IPageSpecification;
getPageStyle?: (page: IPage) => any;
items?: any[];
onPageAdded?: (page: IPage) => void;
onPageRemoved?: (page: IPage) => void;
onPagesUpdated?: (pages: IPage[]) => void;
onRenderCell?: (item?: any, index?: number, isScrolling?: boolean) => React.ReactNode;
onRenderPage?: (pageProps: IPageProps, defaultRender?: IRenderFunction<IPageProps>) => React.ReactNode;
onShouldVirtualize?: (props: IListProps) => boolean;
getPageStyle?: (page: IPage<T>) => any;
items?: T[];
onPageAdded?: (page: IPage<T>) => void;
onPageRemoved?: (page: IPage<T>) => void;
onPagesUpdated?: (pages: IPage<T>[]) => void;
onRenderCell?: (item?: T, index?: number, isScrolling?: boolean) => React.ReactNode;
onRenderPage?: (pageProps: IPageProps<T>, defaultRender?: IRenderFunction<IPageProps<T>>) => React.ReactNode;
onShouldVirtualize?: (props: IListProps<T>) => boolean;
renderCount?: number;
renderedWindowsAhead?: number;
renderedWindowsBehind?: number;
Expand All @@ -4954,12 +4954,12 @@ export interface IListProps extends React.HTMLAttributes<List | HTMLDivElement>
}

// @public (undocumented)
export interface IListState {
export interface IListState<T = any> {
// (undocumented)
isScrolling?: boolean;
measureVersion?: number;
// (undocumented)
pages?: IPage[];
pages?: IPage<T>[];
}

// @public (undocumented)
Expand Down Expand Up @@ -5347,7 +5347,7 @@ export interface IOverlayStyles {
}

// @public (undocumented)
export interface IPage {
export interface IPage<T = any> {
// (undocumented)
data?: any;
// (undocumented)
Expand All @@ -5357,7 +5357,7 @@ export interface IPage {
// (undocumented)
itemCount: number;
// (undocumented)
items: any[] | undefined;
items: T[] | undefined;
// (undocumented)
key: string;
// (undocumented)
Expand All @@ -5369,8 +5369,8 @@ export interface IPage {
}

// @public (undocumented)
export interface IPageProps extends React.HTMLAttributes<HTMLDivElement>, React.ClassAttributes<HTMLDivElement> {
page: IPage;
export interface IPageProps<T = any> extends React.HTMLAttributes<HTMLDivElement>, React.ClassAttributes<HTMLDivElement> {
page: IPage<T>;
role?: string;
}

Expand Down Expand Up @@ -7545,12 +7545,12 @@ export class LinkBase extends BaseComponent<ILinkProps, any> implements ILink {
}

// @public
export class List extends BaseComponent<IListProps, IListState> implements IList {
constructor(props: IListProps);
export class List<T = any> extends BaseComponent<IListProps<T>, IListState<T>> implements IList {
constructor(props: IListProps<T>);
// (undocumented)
componentDidMount(): void;
// (undocumented)
componentWillReceiveProps(newProps: IListProps): void;
componentWillReceiveProps(newProps: IListProps<T>): void;
// (undocumented)
static defaultProps: {
startIndex: number;
Expand All @@ -7570,7 +7570,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
render(): JSX.Element;
scrollToIndex(index: number, measureItem?: (itemIndex: number) => number, scrollToMode?: ScrollToMode): void;
// (undocumented)
shouldComponentUpdate(newProps: IListProps, newState: IListState): boolean;
shouldComponentUpdate(newProps: IListProps<T>, newState: IListState<T>): boolean;
}

// @public (undocumented)
Expand Down
61 changes: 31 additions & 30 deletions packages/office-ui-fabric-react/src/components/List/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@ const DEFAULT_RENDERED_WINDOWS_AHEAD = 2;
const PAGE_KEY_PREFIX = 'page-';
const SPACER_KEY_PREFIX = 'spacer-';

export interface IListState {
pages?: IPage[];
export interface IListState<T = any> {
pages?: IPage<T>[];

/** The last versionstamp for */
measureVersion?: number;
isScrolling?: boolean;
}

interface IPageCacheItem {
page: IPage;
interface IPageCacheItem<T> {
page: IPage<T>;
pageElement?: JSX.Element;
}

interface IPageCache {
[key: string]: IPageCacheItem;
interface IPageCache<T> {
[key: string]: IPageCacheItem<T>;
}

const EMPTY_RECT = {
Expand Down Expand Up @@ -79,7 +79,7 @@ const _measureScrollRect = _measurePageRect;
* or forcing an update change cause pages to shrink/grow. When these operations occur, we increment a measureVersion
* number, which we associate with cached measurements and use to determine if a remeasure should occur.
*/
export class List extends BaseComponent<IListProps, IListState> implements IList {
export class List<T = any> extends BaseComponent<IListProps<T>, IListState<T>> implements IList {
public static defaultProps = {
startIndex: 0,
onRenderCell: (item: any, index: number, containsFocus: boolean) => <>{(item && item.name) || ''}</>,
Expand Down Expand Up @@ -124,9 +124,9 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
private _measureVersion: number;
private _scrollHeight: number;
private _scrollTop: number;
private _pageCache: IPageCache;
private _pageCache: IPageCache<T>;

constructor(props: IListProps) {
constructor(props: IListProps<T>) {
super(props);

this.state = {
Expand Down Expand Up @@ -304,7 +304,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
}
}

public componentWillReceiveProps(newProps: IListProps): void {
public componentWillReceiveProps(newProps: IListProps<T>): void {
if (
newProps.items !== this.props.items ||
newProps.renderCount !== this.props.renderCount ||
Expand All @@ -321,7 +321,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
}
}

public shouldComponentUpdate(newProps: IListProps, newState: IListState): boolean {
public shouldComponentUpdate(newProps: IListProps<T>, newState: IListState<T>): boolean {
const { pages: oldPages } = this.state;
const { pages: newPages } = newState;
let shouldComponentUpdate = false;
Expand Down Expand Up @@ -377,7 +377,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
);
}

private _shouldVirtualize(props: IListProps = this.props): boolean {
private _shouldVirtualize(props: IListProps<T> = this.props): boolean {
const { onShouldVirtualize } = props;
return !onShouldVirtualize || onShouldVirtualize(props);
}
Expand All @@ -389,7 +389,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
this._pageCache = {};
}

private _renderPage(page: IPage): JSX.Element {
private _renderPage(page: IPage<T>): JSX.Element {
const { usePageCache } = this.props;
let cachedPage;
// if usePageCache is set and cached page element can be found, just return cached page
Expand Down Expand Up @@ -430,7 +430,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
}

/** Generate the style object for the page. */
private _getPageStyle(page: IPage): React.StyleHTMLAttributes<HTMLDivElement> {
private _getPageStyle(page: IPage<T>): React.StyleHTMLAttributes<HTMLDivElement> {
const { getPageStyle } = this.props;

return {
Expand All @@ -443,7 +443,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
};
}

private _onRenderPage = (pageProps: IPageProps, defaultRender?: IRenderFunction<IPageProps>): any => {
private _onRenderPage = (pageProps: IPageProps<T>, defaultRender?: IRenderFunction<IPageProps<T>>): any => {
const { onRenderCell, role } = this.props;

const {
Expand All @@ -459,7 +459,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
const index = startIndex + i;
const item = items[i];

let itemKey = this.props.getKey ? this.props.getKey(item, index) : item && item.key;
let itemKey = this.props.getKey ? this.props.getKey(item, index) : item && (item as any).key;

if (itemKey === null || itemKey === undefined) {
itemKey = index;
Expand Down Expand Up @@ -559,7 +559,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
this.forceUpdate();
}

private _updatePages(props: IListProps = this.props): void {
private _updatePages(props: IListProps<T> = this.props): void {
// console.log('updating pages');

if (!this._requiredRect) {
Expand Down Expand Up @@ -597,7 +597,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList

// Notify the caller that rendering the new pages has completed
if (props.onPagesUpdated) {
props.onPagesUpdated(this.state.pages as IPage[]);
props.onPagesUpdated(this.state.pages as IPage<T>[]);
}
});
}
Expand All @@ -608,12 +608,12 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
* @param newPages - The new pages
* @param props - The props to use
*/
private _notifyPageChanges(oldPages: IPage[], newPages: IPage[], props: IListProps = this.props): void {
private _notifyPageChanges(oldPages: IPage<T>[], newPages: IPage<T>[], props: IListProps<T> = this.props): void {
const { onPageAdded, onPageRemoved } = props;

if (onPageAdded || onPageRemoved) {
const renderedIndexes: {
[index: number]: IPage;
[index: number]: IPage<T>;
} = {};

for (const page of oldPages) {
Expand All @@ -640,7 +640,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
}
}

private _updatePageMeasurements(pages: IPage[]): boolean {
private _updatePageMeasurements(pages: IPage<T>[]): boolean {
let heightChanged = false;

// when not in virtualize mode, we render all the items without page measurement
Expand All @@ -663,7 +663,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
* Given a page, measure its dimensions, update cache.
* @returns True if the height has changed.
*/
private _measurePage(page: IPage): boolean {
private _measurePage(page: IPage<T>): boolean {
let hasChangedHeight = false;
const pageElement = this.refs[page.key] as HTMLElement;
const cachedHeight = this._cachedPageHeights[page.startIndex];
Expand Down Expand Up @@ -700,7 +700,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
}

/** Called when a page has been added to the DOM. */
private _onPageAdded(page: IPage): void {
private _onPageAdded(page: IPage<T>): void {
const { onPageAdded } = this.props;

// console.log('page added', page.startIndex, this.state.pages.map(page => page.key).join(', '));
Expand All @@ -711,7 +711,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
}

/** Called when a page has been removed from the DOM. */
private _onPageRemoved(page: IPage): void {
private _onPageRemoved(page: IPage<T>): void {
const { onPageRemoved } = this.props;

// console.log(' --- page removed', page.startIndex, this.state.pages.map(page => page.key).join(', '));
Expand All @@ -722,14 +722,14 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
}

/** Build up the pages that should be rendered. */
private _buildPages(props: IListProps): IListState {
private _buildPages(props: IListProps<T>): IListState<T> {
let { renderCount } = props;
const { items, startIndex, getPageHeight } = props;

renderCount = this._getRenderCount(props);

const materializedRect = { ...EMPTY_RECT };
const pages: IPage[] = [];
const pages: IPage<T>[] = [];

let itemsPerPage = 1;
let pageTop = 0;
Expand All @@ -755,7 +755,8 @@ export class List extends BaseComponent<IListProps, IListState> implements IList

const pageBottom = pageTop + pageHeight - 1;

const isPageRendered = findIndex(this.state.pages as IPage[], (page: IPage) => !!page.items && page.startIndex === itemIndex) > -1;
const isPageRendered =
findIndex(this.state.pages as IPage<T>[], (page: IPage<T>) => !!page.items && page.startIndex === itemIndex) > -1;
const isPageInAllowedRange = !allowedRect || (pageBottom >= allowedRect.top && pageTop <= allowedRect.bottom!);
const isPageInRequiredRange = !this._requiredRect || (pageBottom >= this._requiredRect.top && pageTop <= this._requiredRect.bottom!);
const isPageVisible = (!isFirstRender && (isPageInRequiredRange || (isPageInAllowedRange && isPageRendered))) || !shouldVirtualize;
Expand Down Expand Up @@ -883,7 +884,7 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
style: React.CSSProperties = {},
data?: any,
isSpacer?: boolean
): IPage {
): IPage<T> {
pageKey = pageKey || PAGE_KEY_PREFIX + startIndex;
const cachedPage = this._pageCache[pageKey];
if (cachedPage && cachedPage.page) {
Expand All @@ -903,14 +904,14 @@ export class List extends BaseComponent<IListProps, IListState> implements IList
};
}

private _getRenderCount(props?: IListProps): number {
private _getRenderCount(props?: IListProps<T>): number {
const { items, startIndex, renderCount } = props || this.props;

return renderCount === undefined ? (items ? items.length - startIndex! : 0) : renderCount;
}

/** Calculate the visible rect within the list where top: 0 and left: 0 is the top/left of the list. */
private _updateRenderRects(props?: IListProps, forceUpdate?: boolean): void {
private _updateRenderRects(props?: IListProps<T>, forceUpdate?: boolean): void {
props = props || this.props;
const { renderedWindowsAhead, renderedWindowsBehind } = props;
const { pages } = this.state;
Expand Down
Loading

0 comments on commit 5ad8b88

Please sign in to comment.