import classNames from "classnames" ; import * as React from "react" ; import { Boundary } from "../../common/boundary" ; import * as Classes from "../../common/classes" ; import { OVERFLOW_LIST_OBSERVE_PARENTS_CHANGED } from "../../common/errors" ; import { DISPLAYNAME_PREFIX , Props } from "../../common/props" ; import { shallowCompareKeys } from "../../common/utils" ; import { ResizeEntry } from "../resize-sensor/resizeObserverTypes" ; import { ResizeSensor } from "../resize-sensor/resizeSensor" ; internal export enum OverflowDirection { NONE , GROW , SHRINK } export type OverflowListProps < T > = IOverflowListProps < T > ; deprecated export interface IOverflowListProps < T > extends Props { default alwaysRenderOverflow ? : boolean ; default collapseFrom ? : Boundary ; /**
     * All items to display in the list. Items that do not fit in the container
     * will be rendered in the overflow instead.
     */ items : T [ ] ; default minVisibleItems ? : number ; default observeParents ? : boolean ; /**
     * Callback invoked when the overflowed items change. This is called once
     * after the DOM has settled, rather that on every intermediate change. It
     * is not invoked if resizing produces an unchanged overflow state.
     */ onOverflow ? : ( overflowItems : T [ ] ) => void ; /**
     * Callback invoked to render the overflowed items. Unlike
     * `visibleItemRenderer`, this prop is invoked once with all items that do
     * not fit in the container.
     *
     * Typical use cases for this prop will put overflowed items in a dropdown
     * menu or display a "+X items" label.
     */ overflowRenderer : ( overflowItems : T [ ] ) => React . ReactNode ; /** CSS properties to apply to the root element. */ style ? : React . CSSProperties ; default tagName ? : keyof JSX . IntrinsicElements ; /**
     * Callback invoked to render each visible item.
     * Remember to set a `key` on the rendered element!
     */ visibleItemRenderer : ( item : T , index : number ) => React . ReactChild ; } export interface IOverflowListState < T > { internal direction : OverflowDirection ; /** Length of last overflow to dedupe `onOverflow` calls during smooth resizing. */ lastOverflowCount : number ; overflow : T [ ] ; visible : T [ ] ; } export class OverflowList < T > extends React . Component < OverflowListProps < T > , IOverflowListState < T > > { public static displayName = `${ DISPLAYNAME_PREFIX }.OverflowList` ; public static defaultProps : Partial < OverflowListProps < any > > = { alwaysRenderOverflow : false , collapseFrom : Boundary . START , minVisibleItems : 0 , } ; public static ofType < U > ( ) { return OverflowList as new ( props ) => OverflowList < U > ; } public state : IOverflowListState < T > = { direction : OverflowDirection . NONE , lastOverflowCount : 0 , overflow : [ ] , visible : this . props . items , } ; /** A cache containing the widths of all elements being observed to detect growing/shrinking */ private previousWidths = new Map < Element , number > ( ) ; private spacer : Element | null = null ; public componentDidMount ( ) { this . repartition ( false ) ; } public shouldComponentUpdate ( _nextProps , nextState ) { return ! ( this . state !== nextState && shallowCompareKeys ( this . state , nextState ) ) ; } public componentDidUpdate ( prevProps , prevState ) { if ( prevProps . observeParents !== this . props . observeParents ) { console . warn ( OVERFLOW_LIST_OBSERVE_PARENTS_CHANGED ) ; } if ( prevProps . collapseFrom !== this . props . collapseFrom || prevProps . items !== this . props . items || prevProps . minVisibleItems !== this . props . minVisibleItems || prevProps . overflowRenderer !== this . props . overflowRenderer || prevProps . alwaysRenderOverflow !== this . props . alwaysRenderOverflow || prevProps . visibleItemRenderer !== this . props . visibleItemRenderer ) { this . setState ( { direction : OverflowDirection . GROW , lastOverflowCount : 0 , overflow : [ ] , visible : this . props . items , } ) ; } if ( ! shallowCompareKeys ( prevState , this . state ) ) { this . repartition ( false ) ; } const { direction , overflow , lastOverflowCount } = this . state ; if ( direction === OverflowDirection . NONE && direction !== prevState . direction && overflow . length !== lastOverflowCount ) { this . props . onOverflow ?. ( overflow ) ; } } public render ( ) { const { className , collapseFrom , observeParents , style , tagName = "div" , visibleItemRenderer } = this . props ; const overflow = this . maybeRenderOverflow ( ) ; const list = React . createElement ( tagName , { className : classNames ( Classes . OVERFLOW_LIST , className ) , style , } , collapseFrom === Boundary . START ? overflow : null , this . state . visible . map ( visibleItemRenderer ) , collapseFrom === Boundary . END ? overflow : null , < div className = { Classes . OVERFLOW_LIST_SPACER } ref = { ref => ( this . spacer = ref ) } />); return ( < ResizeSensor onResize = { this . resize } observeParents = { observeParents } > { list } < /ResizeSensor>); } private maybeRenderOverflow ( ) { const { overflow } = this . state ; if ( overflow . length === 0 && ! this . props . alwaysRenderOverflow ) { return null ; } return this . props . overflowRenderer ( overflow ) ; } private resize = ( entries ) => { const growing = entries . some ( entry => { const previousWidth = this . previousWidths . get ( entry . target ) || 0 ; return entry . contentRect . width > previousWidth ; } ) ; this . repartition ( growing ) ; entries . forEach ( entry => this . previousWidths . set ( entry . target , entry . contentRect . width ) ) ; } ; private repartition ( growing ) { if ( this . spacer == null ) { return ; } if ( growing ) { this . setState ( state => ( { direction : OverflowDirection . GROW , lastOverflowCount : state . direction === OverflowDirection . NONE ? state . overflow . length : state . lastOverflowCount , overflow : [ ] , visible : this . props . items , } ) ) ; } else if ( this . spacer . getBoundingClientRect ( ) . width < 0.9 ) { this . setState ( state => { if ( state . visible . length <= this . props . minVisibleItems ! ) { return null ; } const collapseFromStart = this . props . collapseFrom === Boundary . START ; const visible = state . visible . slice ( ) ; const next = collapseFromStart ? visible . shift ( ) : visible . pop ( ) ; if ( next === undefined ) { return null ; } const overflow = collapseFromStart ? [ ... state . overflow , next ] : [ next , ... state . overflow ] ; return { direction : state . direction === OverflowDirection . NONE ? OverflowDirection . SHRINK : state . direction , overflow , visible , } ; } ) ; } else { this . setState ( { direction : OverflowDirection . NONE } ) ; } } }