import { useMemo } from 'react' ; const GridItemTag = Symbol ( 'Grid Item Tag' ) ; export const FullWidth = 'fill' ; export type Width = number | typeof FullWidth ; export interface GridItem < T > { [ GridItemTag ] : typeof GridItemTag ; value : T ; width : Width ; } export type WidthTable = Record < number , Width > ; export type InsertAfterTable < T > = Record < number , T | GridItem < T > > ; export type InsertRowTable < T > = Record < number , T > ; export interface InsertEveryOptions < T > { frequency : number ; values : ( T | GridItem < T > ) | ( T | GridItem < T > ) [ ] ; } export interface ChunkOptions < T , E = undefined > { widthTable ? : WidthTable ; insertRows ? : InsertRowTable < T > ; insertAfter ? : InsertAfterTable < T > ; insertEvery ? : InsertEveryOptions < T > ; emptyValue ? : E ; } const isGridItem = < T > ( item ) : item is GridItem < T > => ! ! ( item && typeof item === 'object' && GridItemTag in item ) ; const minMaxWidth = ( width , maxWidth ) => { if ( width !== FullWidth && width > maxWidth ) { return maxWidth ; } return width ; } ; const minMaxGridItemWidth = < T > ( item , maxWidth = Infinity ) : GridItem < T > => ( { ... item , width : minMaxWidth ( item . width , maxWidth ) } ) ; export const makeGridItem = < T > ( value , width = 1 , maxWidth = Infinity ) : GridItem < T > => { if ( isGridItem ( value ) ) { return minMaxGridItemWidth ( value , maxWidth ) ; } return { [ GridItemTag ] : GridItemTag , value , width : minMaxWidth ( width , maxWidth ) } ; } ; export const getAbsoluteWidth = ( width , totalColumns ) : number => width === FullWidth || width > totalColumns ? totalColumns : width ; const makeSizer = ( widthTable ? ) => { return < T > ( itIndex , insertedItem ) => { if ( insertedItem !== undefined ) { return isGridItem ( insertedItem ) ? insertedItem . width : 1 ; } return widthTable ?. [ itIndex ] ?? 1 ; } ; } ; const makeInserter = < T > ( insertIndexes ? , insertEvery ? , insertRows ? ) => { const insertedIndexes = new Set < number > ( ) ; const insertedIntervals = new Set < number > ( ) ; return ( itIndex , columnIndex , row ) : { value : T | GridItem < T > ; hasInserted : ( ) => void ; } | undefined => { const insertedIndex = insertIndexes ?. [ itIndex - 1 ] ; if ( insertedIndex !== undefined && ! insertedIndexes . has ( itIndex ) ) { return { value : insertedIndex , hasInserted : ( ) => insertedIndexes . add ( itIndex ) } ; } const insertedRow = insertRows ?. [ row ] ; if ( columnIndex === 0 && insertedRow ) { return { value : makeGridItem ( insertedRow , FullWidth ) , hasInserted : ( ) => undefined } ; } if ( insertEvery !== undefined ) { const iteration = Math . floor ( itIndex / insertEvery . frequency ) ; const shouldInsert = itIndex > 0 && itIndex % insertEvery . frequency === 0 ; if ( shouldInsert && ! insertedIntervals . has ( iteration ) ) { const hasInserted = ( ) => insertedIntervals . add ( iteration ) ; if ( Array . isArray ( insertEvery ?. values ) ) { const value = insertEvery . values [ iteration ] ; if ( value === undefined ) { return undefined ; } return { value , hasInserted } ; } return { value : insertEvery . values , hasInserted } ; } } return undefined ; } ; } ; const gridHasSpace = ( itemWidth , columnsRemaining , totalColumns ) => { if ( totalColumns === columnsRemaining ) { return true ; } if ( itemWidth === FullWidth ) { return false ; } if ( itemWidth <= columnsRemaining ) { return true ; } return false ; } ; export function * chunkGrid < T , E = undefined > ( it , totalColumns , options ? ) { const { emptyValue , widthTable , insertAfter , insertEvery , insertRows } = options ?? { } ; const generator = it [ Symbol . iterator ] ( ) ; let buffer = [ ] ; let row = 0 ; let itIndex = 0 ; let columnIndex = 0 ; const inserter = makeInserter ( insertAfter , insertEvery , insertRows ) ; const sizer = makeSizer ( widthTable ) ; const createEmptyRow = ( size ) => new Array < GridItem < E > > ( size ) . fill ( makeGridItem ( emptyValue as E ) ) ; while ( true ) { const insertedItem = inserter ( itIndex , columnIndex , row ) ; const itemWidth = sizer ( itIndex , insertedItem ?. value ) ; const columnsRemaining = totalColumns - columnIndex ; if ( ! gridHasSpace ( itemWidth , columnsRemaining , totalColumns ) ) { const emptyRow = createEmptyRow ( columnsRemaining ) ; buffer . push ( ... emptyRow ) ; columnIndex += columnsRemaining ; } else if ( insertedItem !== undefined ) { insertedItem . hasInserted ( ) ; const item = makeGridItem ( insertedItem . value ) ; const width = getAbsoluteWidth ( item . width , totalColumns ) ; buffer . push ( item ) ; columnIndex += width ; } else { const result = generator . next ( ) ; if ( result . done ) { break ; } const { value } = result ; const item = makeGridItem ( value , itemWidth ) ; const width = getAbsoluteWidth ( item . width , totalColumns ) ; buffer . push ( item ) ; columnIndex += width ; itIndex += 1 ; } if ( columnIndex >= totalColumns ) { yield buffer ; buffer = [ ] ; columnIndex = 0 ; row += 1 ; } } if ( buffer . length > 0 ) { const columnsRemaining = totalColumns - columnIndex ; const emptyRow = createEmptyRow ( columnsRemaining ) ; buffer . push ( ... emptyRow ) ; yield buffer ; } } export const useGridChunks = < T , E = undefined > ( it , columns , options ? ) => { return useMemo ( ( ) => chunkGrid ( it , columns , options ) , [ it , columns , options ] ) ; } ;