diff --git a/apps/todo-app/src/components/TodoTabs.tsx b/apps/todo-app/src/components/TodoTabs.tsx index 66ce07a2a1e0c..1b97a7ef36a38 100644 --- a/apps/todo-app/src/components/TodoTabs.tsx +++ b/apps/todo-app/src/components/TodoTabs.tsx @@ -61,7 +61,10 @@ export default class TodoTabs extends React.Component { return ev.which === KeyCodes.right; }; - private _onRenderTodoItem(item: ITodoItem): React.ReactElement { - return ; + private _onRenderTodoItem(item?: ITodoItem): React.ReactElement | null { + if (item) { + return ; + } + return null; } } diff --git a/common/changes/office-ui-fabric-react/keco-make-list-generic_2019-03-25-21-58.json b/common/changes/office-ui-fabric-react/keco-make-list-generic_2019-03-25-21-58.json new file mode 100644 index 0000000000000..a0232cba3f6e3 --- /dev/null +++ b/common/changes/office-ui-fabric-react/keco-make-list-generic_2019-03-25-21-58.json @@ -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": "keco@microsoft.com" +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md b/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md index b1c898a712d97..ddb9453b88cf6 100644 --- a/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md +++ b/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md @@ -4930,21 +4930,21 @@ export interface IList { } // @public (undocumented) -export interface IListProps extends React.HTMLAttributes { +export interface IListProps extends React.HTMLAttributes | HTMLDivElement> { className?: string; componentRef?: IRefObject; 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) => React.ReactNode; - onShouldVirtualize?: (props: IListProps) => boolean; + getPageStyle?: (page: IPage) => any; + items?: T[]; + onPageAdded?: (page: IPage) => void; + onPageRemoved?: (page: IPage) => void; + onPagesUpdated?: (pages: IPage[]) => void; + onRenderCell?: (item?: T, index?: number, isScrolling?: boolean) => React.ReactNode; + onRenderPage?: (pageProps: IPageProps, defaultRender?: IRenderFunction>) => React.ReactNode; + onShouldVirtualize?: (props: IListProps) => boolean; renderCount?: number; renderedWindowsAhead?: number; renderedWindowsBehind?: number; @@ -4954,12 +4954,12 @@ export interface IListProps extends React.HTMLAttributes } // @public (undocumented) -export interface IListState { +export interface IListState { // (undocumented) isScrolling?: boolean; measureVersion?: number; // (undocumented) - pages?: IPage[]; + pages?: IPage[]; } // @public (undocumented) @@ -5347,7 +5347,7 @@ export interface IOverlayStyles { } // @public (undocumented) -export interface IPage { +export interface IPage { // (undocumented) data?: any; // (undocumented) @@ -5357,7 +5357,7 @@ export interface IPage { // (undocumented) itemCount: number; // (undocumented) - items: any[] | undefined; + items: T[] | undefined; // (undocumented) key: string; // (undocumented) @@ -5369,8 +5369,8 @@ export interface IPage { } // @public (undocumented) -export interface IPageProps extends React.HTMLAttributes, React.ClassAttributes { - page: IPage; +export interface IPageProps extends React.HTMLAttributes, React.ClassAttributes { + page: IPage; role?: string; } @@ -7545,12 +7545,12 @@ export class LinkBase extends BaseComponent implements ILink { } // @public -export class List extends BaseComponent implements IList { - constructor(props: IListProps); +export class List extends BaseComponent, IListState> implements IList { + constructor(props: IListProps); // (undocumented) componentDidMount(): void; // (undocumented) - componentWillReceiveProps(newProps: IListProps): void; + componentWillReceiveProps(newProps: IListProps): void; // (undocumented) static defaultProps: { startIndex: number; @@ -7570,7 +7570,7 @@ export class List extends BaseComponent 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, newState: IListState): boolean; } // @public (undocumented) diff --git a/packages/office-ui-fabric-react/src/components/List/List.tsx b/packages/office-ui-fabric-react/src/components/List/List.tsx index d93b443991e05..a8d54c4d1b46d 100644 --- a/packages/office-ui-fabric-react/src/components/List/List.tsx +++ b/packages/office-ui-fabric-react/src/components/List/List.tsx @@ -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 { + pages?: IPage[]; /** The last versionstamp for */ measureVersion?: number; isScrolling?: boolean; } -interface IPageCacheItem { - page: IPage; +interface IPageCacheItem { + page: IPage; pageElement?: JSX.Element; } -interface IPageCache { - [key: string]: IPageCacheItem; +interface IPageCache { + [key: string]: IPageCacheItem; } const EMPTY_RECT = { @@ -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 implements IList { +export class List extends BaseComponent, IListState> implements IList { public static defaultProps = { startIndex: 0, onRenderCell: (item: any, index: number, containsFocus: boolean) => <>{(item && item.name) || ''}, @@ -124,9 +124,9 @@ export class List extends BaseComponent implements IList private _measureVersion: number; private _scrollHeight: number; private _scrollTop: number; - private _pageCache: IPageCache; + private _pageCache: IPageCache; - constructor(props: IListProps) { + constructor(props: IListProps) { super(props); this.state = { @@ -304,7 +304,7 @@ export class List extends BaseComponent implements IList } } - public componentWillReceiveProps(newProps: IListProps): void { + public componentWillReceiveProps(newProps: IListProps): void { if ( newProps.items !== this.props.items || newProps.renderCount !== this.props.renderCount || @@ -321,7 +321,7 @@ export class List extends BaseComponent implements IList } } - public shouldComponentUpdate(newProps: IListProps, newState: IListState): boolean { + public shouldComponentUpdate(newProps: IListProps, newState: IListState): boolean { const { pages: oldPages } = this.state; const { pages: newPages } = newState; let shouldComponentUpdate = false; @@ -377,7 +377,7 @@ export class List extends BaseComponent implements IList ); } - private _shouldVirtualize(props: IListProps = this.props): boolean { + private _shouldVirtualize(props: IListProps = this.props): boolean { const { onShouldVirtualize } = props; return !onShouldVirtualize || onShouldVirtualize(props); } @@ -389,7 +389,7 @@ export class List extends BaseComponent implements IList this._pageCache = {}; } - private _renderPage(page: IPage): JSX.Element { + private _renderPage(page: IPage): JSX.Element { const { usePageCache } = this.props; let cachedPage; // if usePageCache is set and cached page element can be found, just return cached page @@ -430,7 +430,7 @@ export class List extends BaseComponent implements IList } /** Generate the style object for the page. */ - private _getPageStyle(page: IPage): React.StyleHTMLAttributes { + private _getPageStyle(page: IPage): React.StyleHTMLAttributes { const { getPageStyle } = this.props; return { @@ -443,7 +443,7 @@ export class List extends BaseComponent implements IList }; } - private _onRenderPage = (pageProps: IPageProps, defaultRender?: IRenderFunction): any => { + private _onRenderPage = (pageProps: IPageProps, defaultRender?: IRenderFunction>): any => { const { onRenderCell, role } = this.props; const { @@ -459,7 +459,7 @@ export class List extends BaseComponent 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; @@ -559,7 +559,7 @@ export class List extends BaseComponent implements IList this.forceUpdate(); } - private _updatePages(props: IListProps = this.props): void { + private _updatePages(props: IListProps = this.props): void { // console.log('updating pages'); if (!this._requiredRect) { @@ -597,7 +597,7 @@ export class List extends BaseComponent 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[]); } }); } @@ -608,12 +608,12 @@ export class List extends BaseComponent 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[], newPages: IPage[], props: IListProps = this.props): void { const { onPageAdded, onPageRemoved } = props; if (onPageAdded || onPageRemoved) { const renderedIndexes: { - [index: number]: IPage; + [index: number]: IPage; } = {}; for (const page of oldPages) { @@ -640,7 +640,7 @@ export class List extends BaseComponent implements IList } } - private _updatePageMeasurements(pages: IPage[]): boolean { + private _updatePageMeasurements(pages: IPage[]): boolean { let heightChanged = false; // when not in virtualize mode, we render all the items without page measurement @@ -663,7 +663,7 @@ export class List extends BaseComponent 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): boolean { let hasChangedHeight = false; const pageElement = this.refs[page.key] as HTMLElement; const cachedHeight = this._cachedPageHeights[page.startIndex]; @@ -700,7 +700,7 @@ export class List extends BaseComponent implements IList } /** Called when a page has been added to the DOM. */ - private _onPageAdded(page: IPage): void { + private _onPageAdded(page: IPage): void { const { onPageAdded } = this.props; // console.log('page added', page.startIndex, this.state.pages.map(page => page.key).join(', ')); @@ -711,7 +711,7 @@ export class List extends BaseComponent implements IList } /** Called when a page has been removed from the DOM. */ - private _onPageRemoved(page: IPage): void { + private _onPageRemoved(page: IPage): void { const { onPageRemoved } = this.props; // console.log(' --- page removed', page.startIndex, this.state.pages.map(page => page.key).join(', ')); @@ -722,14 +722,14 @@ export class List extends BaseComponent implements IList } /** Build up the pages that should be rendered. */ - private _buildPages(props: IListProps): IListState { + private _buildPages(props: IListProps): IListState { let { renderCount } = props; const { items, startIndex, getPageHeight } = props; renderCount = this._getRenderCount(props); const materializedRect = { ...EMPTY_RECT }; - const pages: IPage[] = []; + const pages: IPage[] = []; let itemsPerPage = 1; let pageTop = 0; @@ -755,7 +755,8 @@ export class List extends BaseComponent 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[], (page: IPage) => !!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; @@ -883,7 +884,7 @@ export class List extends BaseComponent implements IList style: React.CSSProperties = {}, data?: any, isSpacer?: boolean - ): IPage { + ): IPage { pageKey = pageKey || PAGE_KEY_PREFIX + startIndex; const cachedPage = this._pageCache[pageKey]; if (cachedPage && cachedPage.page) { @@ -903,14 +904,14 @@ export class List extends BaseComponent implements IList }; } - private _getRenderCount(props?: IListProps): number { + private _getRenderCount(props?: IListProps): 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, forceUpdate?: boolean): void { props = props || this.props; const { renderedWindowsAhead, renderedWindowsBehind } = props; const { pages } = this.state; diff --git a/packages/office-ui-fabric-react/src/components/List/List.types.ts b/packages/office-ui-fabric-react/src/components/List/List.types.ts index 9b1a4244062a5..8d2f20f08c8c3 100644 --- a/packages/office-ui-fabric-react/src/components/List/List.types.ts +++ b/packages/office-ui-fabric-react/src/components/List/List.types.ts @@ -49,7 +49,7 @@ export interface IList { getStartItemIndexInView: () => number; } -export interface IListProps extends React.HTMLAttributes { +export interface IListProps extends React.HTMLAttributes | HTMLDivElement> { /** * Optional callback to access the IList interface. Use this instead of ref for accessing * the public methods and properties of the component. @@ -60,7 +60,7 @@ export interface IListProps extends React.HTMLAttributes className?: string; /** Items to render. */ - items?: any[]; + items?: T[]; /** * Method to call when trying to render an item. @@ -68,7 +68,7 @@ export interface IListProps extends React.HTMLAttributes * @param index - The index of the cell being rendered. * @param isScrolling - True if the list is being scrolled. May be useful for rendering a placeholder if your cells are complex. */ - onRenderCell?: (item?: any, index?: number, isScrolling?: boolean) => React.ReactNode; + onRenderCell?: (item?: T, index?: number, isScrolling?: boolean) => React.ReactNode; /** * Optional callback invoked when List rendering completed. @@ -78,16 +78,16 @@ export interface IListProps extends React.HTMLAttributes * To track individual page Add / Remove use onPageAdded / onPageRemoved instead. * @param pages - The current array of pages in the List. */ - onPagesUpdated?: (pages: IPage[]) => void; + onPagesUpdated?: (pages: IPage[]) => void; /** Optional callback for monitoring when a page is added. */ - onPageAdded?: (page: IPage) => void; + onPageAdded?: (page: IPage) => void; /** Optional callback for monitoring when a page is removed. */ - onPageRemoved?: (page: IPage) => void; + onPageRemoved?: (page: IPage) => void; /** Optional callback to get the item key, to be used on render. */ - getKey?: (item: any, index?: number) => string; + getKey?: (item: T, index?: number) => string; /** * Called by the list to get the specification for a page. @@ -116,7 +116,7 @@ export interface IListProps extends React.HTMLAttributes * Method called by the list to derive the page style object. For spacer pages, the list will derive * the height and passed in heights will be ignored. */ - getPageStyle?: (page: IPage) => any; + getPageStyle?: (page: IPage) => any; /** * In addition to the visible window, how many windowHeights should we render ahead. @@ -149,7 +149,7 @@ export interface IListProps extends React.HTMLAttributes * This benefits larger list scenarios by reducing the DOM on the screen, but can negatively affect performance for smaller lists. * The default implementation will virtualize when this callback is not provided. */ - onShouldVirtualize?: (props: IListProps) => boolean; + onShouldVirtualize?: (props: IListProps) => boolean; /** * The role to assign to the list root element. @@ -161,12 +161,12 @@ export interface IListProps extends React.HTMLAttributes * Called when the List will render a page. * Override this to control how cells are rendered within a page. */ - onRenderPage?: (pageProps: IPageProps, defaultRender?: IRenderFunction) => React.ReactNode; + onRenderPage?: (pageProps: IPageProps, defaultRender?: IRenderFunction>) => React.ReactNode; } -export interface IPage { +export interface IPage { key: string; - items: any[] | undefined; + items: T[] | undefined; startIndex: number; itemCount: number; style: React.CSSProperties; @@ -176,7 +176,7 @@ export interface IPage { isSpacer?: boolean; } -export interface IPageProps extends React.HTMLAttributes, React.ClassAttributes { +export interface IPageProps extends React.HTMLAttributes, React.ClassAttributes { /** * The role being assigned to the rendered page element by the list. */ @@ -184,7 +184,7 @@ export interface IPageProps extends React.HTMLAttributes, React. /** * The allocation data for the page. */ - page: IPage; + page: IPage; } export interface IPageSpecification { diff --git a/packages/office-ui-fabric-react/src/components/List/examples/List.Scrolling.Example.tsx b/packages/office-ui-fabric-react/src/components/List/examples/List.Scrolling.Example.tsx index 5e12175195f91..092673d973dcf 100644 --- a/packages/office-ui-fabric-react/src/components/List/examples/List.Scrolling.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/List/examples/List.Scrolling.Example.tsx @@ -25,7 +25,7 @@ const oddItemHeight = 50; const numberOfItemsOnPage = 10; export class ListScrollingExample extends React.Component { - private _list: List; + private _list: List; constructor(props: IListScrollingExampleProps) { super(props); @@ -157,7 +157,7 @@ export class ListScrollingExample extends React.Component { + private _resolveList = (list: List): void => { this._list = list; };