import { ItemSizer , InstanceInterface , AreaProps , CellInterface , CellMetaData , SelectionArea , } from "./Grid" ; import { Direction , KeyCodes } from "./types" ; export enum Align { start = "start" , end = "end" , center = "center" , auto = "auto" , smart = "smart" } export enum ItemType { row = "row" , column = "column" } export interface IItemMetaData { itemType : ItemType ; offset : number ; index : number ; rowCount : number ; columnCount : number ; rowHeight : ItemSizer ; columnWidth : ItemSizer ; instanceProps : InstanceInterface ; scale : number ; } export const getRowStartIndexForOffset = ( { rowHeight , columnWidth , rowCount , columnCount , instanceProps , offset , scale , } ) : number => { return findNearestItem ( { itemType : ItemType . row , rowHeight , columnWidth , rowCount , columnCount , instanceProps , offset , scale , } ) ; } ; interface IRowStopIndex extends Omit < IItemMetaData , "itemType" | "index" | "offset" | "columnCount" > { startIndex : number ; containerHeight : number ; scrollTop : number ; } export const getRowStopIndexForStartIndex = ( { startIndex , rowCount , rowHeight , columnWidth , scrollTop , containerHeight , instanceProps , scale , } ) : number => { const itemMetadata = getItemMetadata ( { itemType : ItemType . row , rowHeight , columnWidth , index : startIndex , instanceProps , scale , } ) ; const maxOffset = scrollTop + containerHeight ; let offset = itemMetadata . offset + itemMetadata . size ; let stopIndex = startIndex ; while ( stopIndex < rowCount - 1 && offset < maxOffset ) { stopIndex ++ ; offset += getItemMetadata ( { itemType : ItemType . row , rowHeight , columnWidth , index : stopIndex , instanceProps , scale , } ) . size ; } return stopIndex ; } ; export const getColumnStartIndexForOffset = ( { rowHeight , columnWidth , rowCount , columnCount , instanceProps , offset , scale , } ) : number => { return findNearestItem ( { itemType : ItemType . column , rowHeight , columnWidth , rowCount , columnCount , instanceProps , offset , scale , } ) ; } ; interface IColumnStopIndex extends Omit < IItemMetaData , "itemType" | "index" | "offset" | "rowCount" > { startIndex : number ; containerWidth : number ; scrollLeft : number ; } export const getColumnStopIndexForStartIndex = ( { startIndex , rowHeight , columnWidth , instanceProps , containerWidth , scrollLeft , columnCount , scale , } ) : number => { const itemMetadata = getItemMetadata ( { itemType : ItemType . column , index : startIndex , rowHeight , columnWidth , instanceProps , scale , } ) ; const maxOffset = scrollLeft + containerWidth ; let offset = itemMetadata . offset + itemMetadata . size ; let stopIndex = startIndex ; while ( stopIndex < columnCount - 1 && offset < maxOffset ) { stopIndex ++ ; offset += getItemMetadata ( { itemType : ItemType . column , rowHeight , columnWidth , index : stopIndex , instanceProps , scale , } ) . size ; } return stopIndex ; } ; export const getBoundedCells = ( area ) => { const cells = new Set ( ) ; if ( ! area ) return cells ; const { top , bottom , left , right } = area ; for ( let i = top ; i <= bottom ; i ++ ) { for ( let j = left ; j <= right ; j ++ ) { cells . add ( cellIdentifier ( i , j ) ) ; } } return cells ; } ; export const itemKey = ( { rowIndex , columnIndex } ) => `${ rowIndex }:${ columnIndex }` ; export const getRowOffset = ( { index , rowHeight , columnWidth , instanceProps , scale , } ) : number => { return getItemMetadata ( { itemType : ItemType . row , index , rowHeight , columnWidth , instanceProps , scale , } ) . offset ; } ; export const getColumnOffset = ( { index , rowHeight , columnWidth , instanceProps , scale , } ) : number => { return getItemMetadata ( { itemType : ItemType . column , index , rowHeight , columnWidth , instanceProps , scale , } ) . offset ; } ; export const getRowHeight = ( index , instanceProps ) => { return instanceProps . rowMetadataMap [ index ] . size ; } ; export const getColumnWidth = ( index , instanceProps ) => { return instanceProps . columnMetadataMap [ index ] . size ; } ; interface IGetItemMetadata extends Pick < IItemMetaData , "itemType" | "index" | "rowHeight" | "columnWidth" | "instanceProps" | "scale" > { } export const getItemMetadata = ( { itemType , index , rowHeight , columnWidth , instanceProps , scale = 2 , } ) : CellMetaData => { let itemMetadataMap , itemSize , lastMeasuredIndex , recalcIndices ; if ( itemType === "column" ) { itemMetadataMap = instanceProps . columnMetadataMap ; itemSize = columnWidth ; lastMeasuredIndex = instanceProps . lastMeasuredColumnIndex ; recalcIndices = instanceProps . recalcColumnIndices ; } else { itemMetadataMap = instanceProps . rowMetadataMap ; itemSize = rowHeight ; lastMeasuredIndex = instanceProps . lastMeasuredRowIndex ; recalcIndices = instanceProps . recalcRowIndices ; } const recalcWithinBoundsOnly = recalcIndices . length > 0 ; if ( index > lastMeasuredIndex ) { let offset = 0 ; if ( lastMeasuredIndex >= 0 ) { const itemMetadata = itemMetadataMap [ lastMeasuredIndex ] ; offset = itemMetadata . offset + itemMetadata . size ; } for ( let i = lastMeasuredIndex + 1 ; i <= index ; i ++ ) { let size = recalcWithinBoundsOnly ? recalcIndices . includes ( i ) ? itemSize ( i ) * scale : itemMetadataMap [ i ] ?. size || itemSize ( i ) * scale : itemSize ( i ) * scale ; itemMetadataMap [ i ] = { offset , size , } ; offset += size ; } if ( itemType === "column" ) { instanceProps . lastMeasuredColumnIndex = index ; } else { instanceProps . lastMeasuredRowIndex = index ; } } return itemMetadataMap [ index ] ; } ; const findNearestItem = ( { itemType , rowHeight , columnWidth , rowCount , columnCount , instanceProps , offset , scale , } ) : number => { let itemMetadataMap , lastMeasuredIndex ; if ( itemType === "column" ) { itemMetadataMap = instanceProps . columnMetadataMap ; lastMeasuredIndex = instanceProps . lastMeasuredColumnIndex ; } else { itemMetadataMap = instanceProps . rowMetadataMap ; lastMeasuredIndex = instanceProps . lastMeasuredRowIndex ; } const lastMeasuredItemOffset = lastMeasuredIndex > 0 ? itemMetadataMap [ lastMeasuredIndex ] . offset : 0 ; if ( lastMeasuredItemOffset >= offset ) { return findNearestItemBinarySearch ( { itemType , rowHeight , columnWidth , instanceProps , high : lastMeasuredIndex , low : 0 , offset , scale , } ) ; } else { return findNearestItemExponentialSearch ( { itemType , rowHeight , rowCount , columnCount , columnWidth , instanceProps , index : Math . max ( 0 , lastMeasuredIndex ) , offset , scale , } ) ; } } ; interface IBinarySearchArgs extends Omit < IItemMetaData , "index" | "rowCount" | "columnCount" > { high : number ; low : number ; } const findNearestItemBinarySearch = ( { itemType , rowHeight , columnWidth , instanceProps , high , low , offset , scale , } ) : number => { while ( low <= high ) { const middle = low + Math . floor ( ( high - low ) / 2 ) ; const currentOffset = getItemMetadata ( { itemType , rowHeight , columnWidth , index : middle , instanceProps , scale , } ) . offset ; if ( currentOffset === offset ) { return middle ; } else if ( currentOffset < offset ) { low = middle + 1 ; } else if ( currentOffset > offset ) { high = middle - 1 ; } } if ( low > 0 ) { return low - 1 ; } else { return 0 ; } } ; const findNearestItemExponentialSearch = ( { itemType , rowHeight , columnWidth , rowCount , columnCount , instanceProps , index , offset , scale , } ) => { const itemCount = itemType === "column" ? columnCount : rowCount ; let interval = 1 ; while ( index < itemCount && getItemMetadata ( { itemType , rowHeight , columnWidth , index , instanceProps , scale , } ) . offset < offset ) { index += interval ; interval *= 2 ; } return findNearestItemBinarySearch ( { itemType , rowHeight , columnWidth , instanceProps , high : Math . min ( index , itemCount - 1 ) , low : Math . floor ( index / 2 ) , offset , scale , } ) ; } ; export const getEstimatedTotalHeight = ( rowCount , instanceProps ) => { const { estimatedRowHeight } = instanceProps ; let totalSizeOfMeasuredRows = 0 ; let { lastMeasuredRowIndex , rowMetadataMap } = instanceProps ; if ( lastMeasuredRowIndex >= rowCount ) { lastMeasuredRowIndex = rowCount - 1 ; } if ( lastMeasuredRowIndex >= 0 ) { const itemMetadata = rowMetadataMap [ lastMeasuredRowIndex ] ; totalSizeOfMeasuredRows = itemMetadata . offset + itemMetadata . size ; } const numUnmeasuredItems = rowCount - lastMeasuredRowIndex - 1 ; const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedRowHeight ; return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems ; } ; export const getEstimatedTotalWidth = ( columnCount , instanceProps ) => { const { estimatedColumnWidth } = instanceProps ; let totalSizeOfMeasuredRows = 0 ; let { lastMeasuredColumnIndex , columnMetadataMap } = instanceProps ; if ( lastMeasuredColumnIndex >= columnCount ) { lastMeasuredColumnIndex = columnCount - 1 ; } if ( lastMeasuredColumnIndex >= 0 ) { const itemMetadata = columnMetadataMap [ lastMeasuredColumnIndex ] ; totalSizeOfMeasuredRows = itemMetadata . offset + itemMetadata . size ; } const numUnmeasuredItems = columnCount - lastMeasuredColumnIndex - 1 ; const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedColumnWidth ; return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems ; } ; export const cellIdentifier = ( rowIndex , columnIndex ) : string => `${ rowIndex },${ columnIndex }` ; desc param func param limit export function throttle < T extends Function > ( func , limit ) { var lastEventTimestamp = null ; let callable = ( ... args ) => { const now = Date . now ( ) ; if ( ! lastEventTimestamp || now - lastEventTimestamp >= limit ) { lastEventTimestamp = now ; func ( ... args ) ; } } ; return < T > ( < any > callable ) ; } export function debounce < T extends Function > ( cb , wait = 300 ) { let h = 0 ; let callable = ( ... args ) => { clearTimeout ( h ) ; h = window . setTimeout ( ( ) => cb ( ... args ) , wait ) ; } ; return < T > ( < any > callable ) ; } export function rafThrottle ( callback ) { var active = false ; var evt ; var handler = function ( ) { active = false ; callback ( evt ) ; } ; return function handleEvent ( e ) { evt = e ; evt && evt . persist && evt . persist ( ) ; if ( ! active ) { active = true ; requestAnimationFrame ( handler ) ; } } ; } export interface AlignmentProps extends Omit < IItemMetaData , "offset" > { containerHeight : number ; containerWidth : number ; align ? : Align ; scrollOffset : number ; scrollbarSize : number ; frozenOffset : number ; estimatedTotalHeight : number ; estimatedTotalWidth : number ; } export const getOffsetForIndexAndAlignment = ( { itemType , containerHeight , containerWidth , rowHeight , columnWidth , columnCount , rowCount , index , align = Align . smart , scrollOffset , instanceProps , scrollbarSize , frozenOffset = 0 , scale , estimatedTotalHeight , estimatedTotalWidth , } ) : number => { const size = itemType === "column" ? containerWidth : containerHeight ; const itemMetadata = getItemMetadata ( { itemType , rowHeight , columnWidth , index , instanceProps , scale , } ) ; const estimatedTotalSize = itemType === "column" ? estimatedTotalWidth : estimatedTotalHeight ; const maxOffset = Math . max ( 0 , Math . min ( estimatedTotalSize - size , itemMetadata . offset - frozenOffset ) ) ; const minOffset = Math . max ( 0 , itemMetadata . offset - size + scrollbarSize + itemMetadata . size ) ; if ( align === Align . smart ) { if ( scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size ) { align = Align . auto ; } else { align = Align . center ; } } switch ( align ) { case Align . start : return maxOffset ; case Align . end : return minOffset ; case Align . center : return Math . round ( minOffset + ( maxOffset - minOffset ) / 2 ) ; case Align . auto : default : if ( scrollOffset >= minOffset && scrollOffset <= maxOffset ) { return scrollOffset ; } else if ( minOffset > maxOffset ) { return minOffset ; } else if ( scrollOffset < minOffset ) { return minOffset ; } else { return maxOffset ; } } } ; export const getOffsetForColumnAndAlignment = ( props ) => { return getOffsetForIndexAndAlignment ( { itemType : ItemType . column , ... props , } ) ; } ; export const getOffsetForRowAndAlignment = ( props ) => { return getOffsetForIndexAndAlignment ( { itemType : ItemType . row , ... props , } ) ; } ; const hasNativePerformanceNow = typeof performance === "object" && typeof performance . now === "function" ; const now = hasNativePerformanceNow ? ( ) => performance . now ( ) : ( ) => Date . now ( ) ; export type TimeoutID = { id : number ; } ; export function cancelTimeout ( timeoutID ) { cancelAnimationFrame ( timeoutID . id ) ; } param callback param delay export function requestTimeout ( callback , delay ) { const start = now ( ) ; function tick ( ) { if ( now ( ) - start >= delay ) { callback . call ( null ) ; } else { timeoutID . id = requestAnimationFrame ( tick ) ; } } const timeoutID = { id : requestAnimationFrame ( tick ) , } ; return timeoutID ; } export const selectionFromActiveCell = ( activeCell ) : SelectionArea [ ] => { if ( ! activeCell ) return [ ] ; return [ { bounds : { top : activeCell . rowIndex , left : activeCell . columnIndex , bottom : activeCell . rowIndex , right : activeCell . columnIndex , } , } , ] ; } ; param sel export const selectionSpansCells = ( sel ) => { if ( ! sel ) return false ; return sel . bottom !== sel . top || sel . left !== sel . right ; } ; param initialSelection param from param to export const newSelectionFromDrag = ( initialSelection , from , to , topBound = 0 , leftBound = 0 , rowCount , columnCount ) => { const currentBounds = initialSelection . bounds ; const top = Math . max ( topBound , Math . min ( rowCount , to . rowIndex + currentBounds . top - from . rowIndex ) ) ; const left = Math . max ( leftBound , Math . min ( columnCount , to . columnIndex + currentBounds . left - from . columnIndex ) ) ; return { bounds : { top , left , bottom : top + ( currentBounds . bottom - currentBounds . top ) , right : left + ( currentBounds . right - currentBounds . left ) , } , } ; } ; param coords param activeCell param selection export const clampCellCoords = ( coords , activeCell , selection ) => { if ( activeCell ) { coords . rowIndex = Math . max ( activeCell . rowIndex , coords . rowIndex ) ; coords . columnIndex = Math . min ( activeCell . columnIndex , coords . columnIndex ) ; } if ( selection ) { coords . rowIndex = Math . min ( selection . bounds . bottom , Math . max ( selection . bounds . top , coords . rowIndex ) ) ; coords . columnIndex = Math . min ( selection . bounds . right , Math . max ( selection . bounds . left , coords . columnIndex ) ) ; } return coords ; } ; param i export const numberToAlphabet = ( i ) : string => { return ( ( i >= 26 ? numberToAlphabet ( ( ( i / 26 ) >> 0 ) - 1 ) : "" ) + "abcdefghijklmnopqrstuvwxyz" [ i % 26 >> 0 ] ) . toUpperCase ( ) ; } ; param rows export const prepareClipboardData = ( rows ) : [ string , string ] => { const html = [ "<table>" ] ; const csv = [ ] ; const sanitizeCell = ( value ) => { if ( isNull ( value ) ) return "" ; return value ; } ; rows . forEach ( ( row ) => { html . push ( "<tr>" ) ; const csvRow = [ ] ; row . forEach ( ( cell ) => { html . push ( `<td>${ sanitizeCell ( cell ) }</td>` ) ; csvRow . push ( `${ castToString ( cell ) ?. replace ( /"/g , '""' ) }` ) ; } ) ; csv . push ( csvRow . join ( "," ) ) ; html . push ( "</tr>" ) ; } ) ; html . push ( "</table>" ) ; return [ html . join ( "" ) , csv . join ( "\n" ) ] ; } ; param activeCellBounds param selectionBounds param direction export const findNextCellWithinBounds = ( activeCellBounds , selectionBounds , direction = Direction . Right ) : CellInterface | null => { const intersects = areaIntersects ( activeCellBounds , selectionBounds ) ; if ( ! intersects ) return null ; let rowIndex , columnIndex ; let nextActiveCell = null ; if ( direction === Direction . Right ) { rowIndex = activeCellBounds . top ; columnIndex = activeCellBounds . left + 1 ; if ( columnIndex > selectionBounds . right ) { rowIndex = rowIndex + 1 ; columnIndex = selectionBounds . left ; if ( rowIndex > selectionBounds . bottom ) { rowIndex = selectionBounds . top ; } } nextActiveCell = { rowIndex , columnIndex } ; } if ( direction === Direction . Left ) { rowIndex = activeCellBounds . bottom ; columnIndex = activeCellBounds . left - 1 ; if ( columnIndex < selectionBounds . left ) { rowIndex = rowIndex - 1 ; columnIndex = selectionBounds . right ; if ( rowIndex < selectionBounds . top ) { rowIndex = selectionBounds . bottom ; } } nextActiveCell = { rowIndex , columnIndex } ; } if ( direction === Direction . Down ) { rowIndex = activeCellBounds . bottom + 1 ; columnIndex = activeCellBounds . left ; if ( rowIndex > selectionBounds . bottom ) { columnIndex = activeCellBounds . left + 1 ; rowIndex = selectionBounds . top ; if ( columnIndex > selectionBounds . right ) { columnIndex = selectionBounds . left ; } } nextActiveCell = { rowIndex , columnIndex } ; } if ( direction === Direction . Up ) { rowIndex = activeCellBounds . top - 1 ; columnIndex = activeCellBounds . left ; if ( rowIndex < selectionBounds . top ) { columnIndex = activeCellBounds . left - 1 ; rowIndex = selectionBounds . bottom ; if ( columnIndex < selectionBounds . left ) { columnIndex = selectionBounds . right ; } } nextActiveCell = { rowIndex , columnIndex } ; } return nextActiveCell ; } ; param area1 param area2 export const areaIntersects = ( area1 , area2 ) : boolean => { if ( area1 . left > area2 . right || area2 . left > area1 . right ) { return false ; } if ( area1 . top > area2 . bottom || area2 . top > area1 . bottom ) { return false ; } return true ; } ; param needle param haystack export const areaInsideArea = ( needle , haystack ) => { return ( needle . top >= haystack . top && needle . bottom <= haystack . bottom && needle . left >= haystack . left && needle . right <= haystack . right ) ; } ; param area1 param area2 export const isAreasEqual = ( area1 , area2 ) => { if ( area1 === void 0 || area2 === void 0 ) { return false ; } return ( area1 . bottom === area2 . bottom && area1 . top === area2 . top && area1 . left === area2 . left && area1 . right === area2 . right ) ; } ; param area param boundGetter export const extendAreaToMergedCells = ( _area , mergedCells ) : AreaProps => { const area = { ... _area } ; for ( const bounds of mergedCells ) { if ( areaIntersects ( area , bounds ) ) { area . top = Math . min ( area . top , bounds . top ) ; area . bottom = Math . max ( area . bottom , bounds . bottom ) ; area . right = Math . max ( area . right , bounds . right ) ; area . left = Math . min ( area . left , bounds . left ) ; } } return area ; } ; param start param end returns export const cellRangeToBounds = ( start , end , spanMerges = true , getCellBounds ) => { let top = Math . min ( start . rowIndex , end . rowIndex ) ; let bottom = Math . max ( start . rowIndex , end . rowIndex ) ; let left = Math . min ( start . columnIndex , end . columnIndex ) ; let right = Math . max ( start . columnIndex , end . columnIndex ) ; /**
     * The idea is that
     * We do 2 loops >
     * Left to Right and then top to bottom
     *  => Find top cell and bottom cell and check
     * if there are any merged cells at the edge
     * Then keep extending our top and bottom bounds accordingly
     *
     * Same goes for Top to bottom
     *  => Find left most and right most cells
     */ if ( spanMerges ) { for ( let columnIndex = left ; columnIndex <= right ; columnIndex ++ ) { const topCell = getCellBounds ( { rowIndex : top , columnIndex } ) ; const bottomCell = getCellBounds ( { rowIndex : bottom , columnIndex } ) ; bottom = Math . max ( topCell . bottom , bottomCell . bottom , bottom ) ; top = Math . min ( topCell . top , bottomCell . top , top ) ; } for ( let rowIndex = top ; rowIndex <= bottom ; rowIndex ++ ) { const topCell = getCellBounds ( { rowIndex , columnIndex : left } ) ; const bottomCell = getCellBounds ( { rowIndex , columnIndex : right } ) ; right = Math . max ( topCell . right , bottomCell . right , right ) ; left = Math . min ( topCell . left , bottomCell . left , left ) ; } } return { top , left , right , bottom , } ; } ; /**
 * Check if its being rendered in Browser or SSR
 */ export const canUseDOM = ! ! ( typeof window !== "undefined" && window . document && window . document . createElement ) ; /**
 * Simple Canvas element to measure text size
 *
 * Usage
 *
 * ```
 * const textSizer = new AutoSizer()
 * textSizer.measureText('Hello world').width
 * ```
 */ interface AutoSizerProps { fontFamily ? : string ; fontSize ? : number ; fontWeight ? : string ; fontStyle ? : string ; lineHeight ? : number ; scale ? : number ; } type IOptions = { [ key ] : any ; } ; export const AutoSizerCanvas = ( defaults = { } ) => { const { fontFamily = "Arial" , fontSize = 12 , fontWeight = "" , fontStyle = "" , lineHeight = 16 , scale = 1 , } = defaults ; var o = { fontFamily , fontSize , fontWeight , fontStyle , lineHeight , scale , } ; const canvas = canUseDOM && < HTMLCanvasElement > document . createElement ( "canvas" ) ; const context = canvas ? canvas . getContext ( "2d" ) : null ; const setFont = ( options = { } ) => { for ( const key in options ) { o [ key ] = options [ key ] ?? o [ key ] ; } if ( context ) { context . font = `${ o . fontStyle } ${ o . fontWeight } ${ o . fontSize * o . scale }px ${ o . fontFamily }` ; } } ; const getWidthOfLongestText = ( text ) => { let width = 0 ; let height = 0 ; if ( text === void 0 ) return { width , height } ; const lines = text . split ( "\n" ) ; for ( let i = 0 ; i < lines . length ; i ++ ) { const line = lines [ i ] ; const lineWidth = context ?. measureText ( line ) . width ?? 0 ; width = Math . max ( width , lineWidth ) ; height += o . fontSize * 1.2 * o . scale ; } return { width : Math . ceil ( width ) , height : Math . ceil ( height ) } ; } ; const measureText = ( text ) => getWidthOfLongestText ( castToString ( text ) ) ; const reset = ( ) => setFont ( defaults ) ; setFont ( o ) ; return { context , measureText , setFont , reset , } ; } ; export const autoSizerCanvas = AutoSizerCanvas ( ) ; export const isNull = ( value ) => value === void 0 || value === null || value === "" ; export const isEqualCells = ( a , b ) => { if ( isNull ( a ) || isNull ( b ) || a === null || b === null ) return false ; return a . rowIndex === b . rowIndex && a . columnIndex === b . columnIndex ; } ; param cell param bounds export const isCellWithinBounds = ( cell , bounds ) => { if ( cell . rowIndex < bounds . top || cell . rowIndex > bounds . bottom ) { return false ; } if ( cell . columnIndex < bounds . left || cell . columnIndex > bounds . right ) { return false ; } return true ; } ; param rowIndex param direction export type HiddenType = ( i : number ) => boolean ; export const clampIndex = ( index , isHidden , direction ) => { switch ( direction ) { case Direction . Right : case Direction . Down : let hidden = isHidden ?. ( index ) ; while ( hidden === true ) { hidden = isHidden ?. ( ++ index ) ; } break ; case Direction . Left : case Direction . Up : { let hidden = isHidden ?. ( index ) ; while ( hidden === true ) { hidden = isHidden ?. ( -- index ) ; } break ; } } return index ; } ; type ValueGetter = ( cell : CellInterface ) => React . ReactText | undefined ; param activeCell param getValue param isHidden param direction param limit export const findNextContentfulCell = ( activeCell , getValue , isHidden , direction , limit ) => { var { rowIndex , columnIndex } = activeCell ; switch ( direction ) { case Direction . Down : { rowIndex = clampIndex ( Math . min ( rowIndex + 1 , limit ) , isHidden , direction ) ; let value = getValue ( { rowIndex , columnIndex } ) ; while ( isNull ( value ) && rowIndex < limit ) { rowIndex = clampIndex ( Math . min ( ++ rowIndex , limit ) , isHidden , direction ) ; value = getValue ( { rowIndex , columnIndex } ) ; } return { rowIndex , columnIndex } ; } case Direction . Up : { rowIndex = clampIndex ( Math . max ( rowIndex - 1 , limit ) , isHidden , direction ) ; let value = getValue ( { rowIndex , columnIndex } ) ; while ( isNull ( value ) && rowIndex > limit ) { rowIndex = clampIndex ( Math . max ( -- rowIndex , limit ) , isHidden , direction ) ; value = getValue ( { rowIndex , columnIndex } ) ; } return { rowIndex , columnIndex } ; } case Direction . Right : { columnIndex = clampIndex ( Math . min ( columnIndex + 1 , limit ) , isHidden , direction ) ; let value = getValue ( { rowIndex , columnIndex } ) ; while ( isNull ( value ) && columnIndex < limit ) { columnIndex = clampIndex ( Math . min ( ++ columnIndex , limit ) , isHidden , direction ) ; value = getValue ( { rowIndex , columnIndex } ) ; } return { rowIndex , columnIndex } ; } case Direction . Left : { columnIndex = clampIndex ( Math . max ( columnIndex - 1 , limit ) , isHidden , direction ) ; let value = getValue ( { rowIndex , columnIndex } ) ; while ( isNull ( value ) && columnIndex > limit ) { columnIndex = clampIndex ( Math . max ( -- columnIndex , limit ) , isHidden , direction ) ; value = getValue ( { rowIndex , columnIndex } ) ; } return { rowIndex , columnIndex } ; } default : return activeCell ; } } ; param activeCell param getValue param isHidden param direction param limit export const findLastContentfulCell = ( activeCell , getValue , isHidden , direction , limit ) : CellInterface => { var { rowIndex , columnIndex } = activeCell ; switch ( direction ) { case Direction . Down : { rowIndex = clampIndex ( Math . min ( rowIndex + 1 , limit ) , isHidden , direction ) ; let value = getValue ( { rowIndex , columnIndex } ) ; while ( ! isNull ( value ) && rowIndex < limit ) { rowIndex = clampIndex ( Math . min ( ++ rowIndex , limit ) , isHidden , direction ) ; value = getValue ( { rowIndex , columnIndex } ) ; } return { columnIndex , rowIndex : isNull ( getValue ( { columnIndex , rowIndex } ) ) ? rowIndex - 1 : rowIndex , } ; } case Direction . Up : { rowIndex = clampIndex ( Math . max ( rowIndex - 1 , limit ) , isHidden , direction ) ; let value = getValue ( { rowIndex , columnIndex } ) ; while ( ! isNull ( value ) && rowIndex > limit ) { rowIndex = clampIndex ( Math . max ( -- rowIndex , limit ) , isHidden , direction ) ; value = getValue ( { rowIndex , columnIndex } ) ; } return { columnIndex , rowIndex : isNull ( getValue ( { columnIndex , rowIndex } ) ) ? rowIndex + 1 : rowIndex , } ; } case Direction . Right : { columnIndex = clampIndex ( Math . min ( columnIndex + 1 , limit ) , isHidden , direction ) ; let value = getValue ( { rowIndex , columnIndex } ) ; while ( ! isNull ( value ) && columnIndex < limit ) { columnIndex = clampIndex ( Math . min ( ++ columnIndex , limit ) , isHidden , direction ) ; value = getValue ( { rowIndex , columnIndex } ) ; } return { rowIndex , columnIndex : isNull ( getValue ( { columnIndex , rowIndex } ) ) ? columnIndex - 1 : columnIndex , } ; } case Direction . Left : { columnIndex = clampIndex ( Math . max ( columnIndex - 1 , limit ) , isHidden , direction ) ; let value = getValue ( { rowIndex , columnIndex } ) ; while ( ! isNull ( value ) && columnIndex > limit ) { columnIndex = clampIndex ( Math . max ( -- columnIndex , limit ) , isHidden , direction ) ; value = getValue ( { rowIndex , columnIndex } ) ; } return { rowIndex , columnIndex : isNull ( getValue ( { columnIndex , rowIndex } ) ) ? columnIndex + 1 : columnIndex , } ; } default : return activeCell ; } } ; /**
 * Ex
 */ export const findNextCellInDataRegion = ( activeCell , getValue , isHidden , direction , limit ) : number => { var { rowIndex , columnIndex } = activeCell ; const isCurrentCellEmpty = isNull ( getValue ( activeCell ) ) ; const didWeReachTheEdge = ( cur , next ) : boolean => { return ( cur && next ) || ( cur && ! next ) || ( ! cur && next ) ; } ; switch ( direction ) { case Direction . Down : { const nextCellValue = getValue ( { rowIndex : rowIndex + 1 , columnIndex } ) ; const isNextCellEmpty = isNull ( nextCellValue ) ; const isEdge = didWeReachTheEdge ( isCurrentCellEmpty , isNextCellEmpty ) ; const nextCell = isEdge ? findNextContentfulCell ( activeCell , getValue , isHidden , direction , limit ) : findLastContentfulCell ( activeCell , getValue , isHidden , direction , limit ) ; return nextCell ?. rowIndex ; } case Direction . Up : { const nextCellValue = getValue ( { rowIndex : rowIndex - 1 , columnIndex } ) ; const isNextCellEmpty = isNull ( nextCellValue ) ; const isEdge = didWeReachTheEdge ( isCurrentCellEmpty , isNextCellEmpty ) ; const nextCell = isEdge ? findNextContentfulCell ( activeCell , getValue , isHidden , direction , limit ) : findLastContentfulCell ( activeCell , getValue , isHidden , direction , limit ) ; return nextCell ?. rowIndex ; } case Direction . Right : { const nextCellValue = getValue ( { rowIndex , columnIndex : columnIndex + 1 , } ) ; const isNextCellEmpty = isNull ( nextCellValue ) ; const isEdge = didWeReachTheEdge ( isCurrentCellEmpty , isNextCellEmpty ) ; const nextCell = isEdge ? findNextContentfulCell ( activeCell , getValue , isHidden , direction , limit ) : findLastContentfulCell ( activeCell , getValue , isHidden , direction , limit ) ; return nextCell ?. columnIndex ; } case Direction . Left : { const nextCellValue = getValue ( { rowIndex , columnIndex : columnIndex - 1 , } ) ; const isNextCellEmpty = isNull ( nextCellValue ) ; const isEdge = didWeReachTheEdge ( isCurrentCellEmpty , isNextCellEmpty ) ; const nextCell = isEdge ? findNextContentfulCell ( activeCell , getValue , isHidden , direction , limit ) : findLastContentfulCell ( activeCell , getValue , isHidden , direction , limit ) ; return nextCell ?. columnIndex ; } } } ; export const focusableNodeNames = new Set ( [ "INPUT" , "TEXTAREA" , "SELECT" ] ) ; param value export const castToString = ( value ) : string | undefined => { if ( value === null || value === void 0 ) return void 0 ; return typeof value !== "string" ? "" + value : value ; } ; export const isArrowKey = ( keyCode ) => { return [ KeyCodes . Up , KeyCodes . Down , KeyCodes . Left , KeyCodes . Right ] . includes ( keyCode ) ; } ;