import { Iterable } from 'ix'; import * as React from 'react'; import { IterableLike } from '../../../WebRx'; import { PanelFragment, PanelItemContext, PanelItemProps, PanelRenderProps, StackPanel, } from '../Panel'; export type ViewTemplate = ( itemsPanel: PanelFragment, itemsPresenter: ItemsPresenter, ) => JSX.Element | null | false; export type ItemsPanelTemplate = ( itemTemplates: PanelFragment[], itemsPresenter: ItemsPresenter, items: T[] | undefined, ) => PanelFragment; export interface ItemsPresenterTemplateProps { /** * template that wraps the entire control. * use this to compose the exterior of the the view. * render the items presenter where you want the items panel located. */ viewTemplate?: ViewTemplate; /** * template to render panel responsible for items layout. * this template can control how items are rendered next to one another * (i.e., wrapping, stack, grid, etc...) */ itemsPanelTemplate?: ItemsPanelTemplate; /** * template to render each item */ itemTemplate?: (item: T, index: number, context?: any) => PanelFragment; } export interface ItemsPresenterSourceProps { /** * data source of items to render * if omitted then component children is used in place */ itemsSource?: IterableLike; } export interface ItemsPresenterProps< T = {}, TContext extends PanelItemContext = PanelItemContext > extends ItemsPresenterTemplateProps, ItemsPresenterSourceProps, PanelItemProps, PanelRenderProps {} export interface ItemsPresenterComponentProps extends React.HTMLProps, ItemsPresenterProps { fill?: boolean; } export class ItemsPresenter extends React.Component< ItemsPresenterComponentProps > { public static displayName = 'ItemsPresenter'; public static defaultItemTemplate(item: {}, index: number, context?: any) { return
{String.stringify(item)}
; } public static defaultPanelTemplate( itemTemplates: PanelFragment[], itemsPresenter: ItemsPresenter, ) { return ( {itemTemplates} ); } public static defaultViewTemplate( itemsPanel: PanelFragment, itemsPresenter: ItemsPresenter, ) { const { className, props, rest } = itemsPresenter.restProps(x => { const { itemsSource, viewTemplate, itemsPanelTemplate, itemTemplate, itemClassName, itemStyle, itemProps, compact, emptyContent, } = x; return { itemsSource, viewTemplate, itemsPanelTemplate, itemTemplate, itemClassName, itemStyle, itemProps, compact, emptyContent, }; }); // allow items presenter to optionally fill its child panel by omitting the presenter wrapper if ( React.isValidElement<{ fill?: boolean }>(itemsPanel) && itemsPanel.props.fill ) { const { fill, ...panelProps } = itemsPanel.props; return ; } return (
{itemsPanel}
); } render() { return this.renderViewTemplate(); } protected getItemsPanelTemplate(): ItemsPanelTemplate { if (this.props.itemsPanelTemplate != null) { return this.props.itemsPanelTemplate; } if ( this.props.itemsSource != null && React.Children.count(this.props.children) === 1 ) { const itemsPanel: PanelFragment = React.Children.only( this.props.children, ); if (React.isValidElement(itemsPanel)) { return items => { const children = items.concat( React.Children.toArray(itemsPanel.props.children), ); return React.cloneElement(itemsPanel, {}, children); }; } return () => itemsPanel; } return ItemsPresenter.defaultPanelTemplate; } protected renderItemTemplates() { const template = this.props.itemTemplate || ItemsPresenter.defaultItemTemplate; if (this.props.itemsSource == null) { return { items: undefined, itemTemplates: React.Children.toArray(this.props.children), }; } const items = Iterable.from(this.props.itemsSource).toArray(); const itemTemplates = items.map((x, i) => { const item = template(x, i); return item != null && React.isValidElement(item) && item.key == null ? React.cloneElement(item, { key: i }) : item; }); return { items, itemTemplates }; } protected renderPanelTemplate(): PanelFragment { const template = this.getItemsPanelTemplate(); const { items, itemTemplates } = this.renderItemTemplates(); return template(itemTemplates, this, items); } protected renderViewTemplate(): JSX.Element | null | false { const template = this.props.viewTemplate || ItemsPresenter.defaultViewTemplate; const itemsPanel = this.renderPanelTemplate(); if (React.isValidElement(itemsPanel)) { const itemsPresenterPanelProps = Object.trim({ itemClassName: this.props.itemClassName, itemStyle: this.props.itemStyle, itemProps: this.props.itemProps, compact: this.props.compact, emptyContent: this.props.emptyContent, fill: this.props.fill, }); const itemsPanelProps = React.isValidElement(itemsPanel) ? itemsPanel.props : {}; return template( React.cloneElement(itemsPanel, { ...itemsPresenterPanelProps, ...itemsPanelProps, }), this, ); } return template(itemsPanel, this); } }