import React , { MouseEvent , RefObject , useEffect , useRef , useState } from 'react' ; import { useForceUpdate } from '../../basic/utils' ; import { DRAG_DEVIATION , FILLER_MIN_WIDTH , HEADER_HEIGHT , MAX_COLUMN_WIDTH , MIN_COLUMN_WIDTH , RESIZE_DEVIATION , ROW_HEIGHT , ROW_NO_WIDTH } from '../constants' ; import { useGridEventBus } from '../grid-event-bus' ; import { GridEventTypes } from '../grid-event-bus-types' ; import { ColumnDefs , ColumnSortBy , DataColumnDef , DataSetState , SelectionRef } from '../types' ; import { Grid } from './grid' ; import { GridDragColumn } from './grid-drag-column' ; import { GridScrollShade } from './grid-scroll-shade' ; import { GridSelection } from './grid-selection' ; import { GridResizeShade , GridWrapperContainer } from './widgets' ; enum Behavior { NONE = 'none' , PICK_COLUMN = 'pick-column' , CAN_RESIZE = 'can-resize' , RESIZING = 'resizing' , READY_TO_DRAG = 'ready-to-drag' , DRAGGING = 'dragging' } interface PickedColumn { column : DataColumnDef ; offsetX : number ; originalWidth : number ; } interface PickColumnOptions { wrapperLeft : number ; table : HTMLDivElement ; mouseClientX : number ; columnDefs : ColumnDefs ; isFixTable : boolean ; rowNoColumnWidth : number ; } const findDataTable = ( element ) : HTMLDivElement | null => { const widgetType = element . getAttribute ( 'data-widget' ) ; if ( widgetType !== 'dataset-grid' ) { return element . closest ( 'div[data-widget="dataset-grid"]' ) as HTMLDivElement ; } return element as HTMLDivElement ; } ; const computeTableColumnsRightPositions = ( displayColumns ) => { const widths = displayColumns . map ( column => column . width ) ; for ( let index = 1 , count = widths . length ; index < count ; index ++ ) { widths [ index ] += widths [ index - 1 ] ; } return widths ; } ; const computeAndSetCursor = ( options ) => { const { table , mouseClientX , mouseClientY , isFixTable , rowNoColumnWidth , displayColumns , fixColumns , changeBehavior } = options ; const { top : containerTop , left : containerLeft } = table . getBoundingClientRect ( ) ; if ( mouseClientY - containerTop > HEADER_HEIGHT ) { changeBehavior ( Behavior . NONE ) ; return ; } const left = mouseClientX - containerLeft ; if ( isFixTable && left <= rowNoColumnWidth ) { changeBehavior ( Behavior . NONE ) ; return ; } const widths = computeTableColumnsRightPositions ( displayColumns ) ; if ( ! isFixTable && left > widths [ widths . length - 1 ] + RESIZE_DEVIATION - table . scrollLeft ) { changeBehavior ( Behavior . NONE ) ; return ; } const offsetLeft = isFixTable ? left - rowNoColumnWidth : left ; let canResize ; if ( isFixTable ) { canResize = widths . some ( width => Math . abs ( width - offsetLeft ) <= RESIZE_DEVIATION ) ; } else { canResize = ( fixColumns . length !== 0 && offsetLeft <= RESIZE_DEVIATION ) || widths . some ( width => Math . abs ( width - offsetLeft - table . scrollLeft ) <= RESIZE_DEVIATION ) ; } changeBehavior ( canResize ? Behavior . CAN_RESIZE : Behavior . PICK_COLUMN ) ; } ; const findPickedColumn = ( options ) : PickedColumn => { const { wrapperLeft , table , mouseClientX , columnDefs , isFixTable , rowNoColumnWidth , matchColumnIndex } = options ; const tableColumnDefs = isFixTable ? columnDefs . fixed : columnDefs . data ; let offsetLeft ; if ( isFixTable ) { offsetLeft = mouseClientX - table . getBoundingClientRect ( ) . left - rowNoColumnWidth ; } else { offsetLeft = mouseClientX - table . getBoundingClientRect ( ) . left + table . scrollLeft ; } const widths = computeTableColumnsRightPositions ( tableColumnDefs ) ; const index = matchColumnIndex ( widths , offsetLeft ) ; return { column : tableColumnDefs [ index ] , offsetX : mouseClientX - wrapperLeft , originalWidth : tableColumnDefs [ index ] . width } ; } ; const findPickedColumnForResize = ( options ) : PickedColumn => { const { wrapperLeft , table , mouseClientX , columnDefs , isFixTable } = options ; if ( ! isFixTable && mouseClientX - table . getBoundingClientRect ( ) . left <= RESIZE_DEVIATION ) { return { column : columnDefs . fixed [ columnDefs . fixed . length - 1 ] , offsetX : mouseClientX - wrapperLeft , originalWidth : columnDefs . fixed [ columnDefs . fixed . length - 1 ] . width } ; } const matchColumnIndex = ( widths , offsetLeft ) => { return widths . findIndex ( width => Math . abs ( width - offsetLeft ) <= RESIZE_DEVIATION ) ; } ; return findPickedColumn ( { ... options , matchColumnIndex } ) ; } ; const findPickedColumnForDrag = ( options ) : PickedColumn => { const matchColumnIndex = ( widths , offsetLeft ) => { return widths . findIndex ( ( width , index ) => { return width > offsetLeft && ( index === 0 || widths [ index - 1 ] < offsetLeft ) ; } ) ; } ; return findPickedColumn ( { ... options , matchColumnIndex } ) ; } ; const useDecorateFixStyle = ( options ) => { const { fixTableRef , dataTableRef } = options ; const arrangeFixedTableStyle = ( ) => { if ( ! dataTableRef . current || ! fixTableRef . current ) { return ; } const dataTable = dataTableRef . current ; const fixTable = fixTableRef . current ; const scrollBarHeight = dataTable . offsetHeight - dataTable . clientHeight ; fixTable . style . height = `calc(100% - ${ scrollBarHeight }px)` ; fixTable . style . boxShadow = `0 1px 0 0 var(--border-color)` ; } ; useEffect ( ( ) => { if ( ! dataTableRef . current || ! fixTableRef . current ) { return ; } const dataTable = dataTableRef . current ; const fixTable = fixTableRef . current ; const onDataTableScroll = ( ) => { arrangeFixedTableStyle ( ) ; fixTable . scrollTop = dataTable . scrollTop ; } ; setTimeout ( arrangeFixedTableStyle , 100 ) ; dataTable . addEventListener ( 'scroll' , onDataTableScroll ) ; const onFixTableScroll = ( ) => dataTable . scrollTop = fixTable . scrollTop ; fixTable . addEventListener ( 'scroll' , onFixTableScroll ) ; return ( ) => { dataTable . removeEventListener ( 'scroll' , onDataTableScroll ) ; fixTable . removeEventListener ( 'scroll' , onFixTableScroll ) ; } ; } ) ; return arrangeFixedTableStyle ; } ; export const GridWrapper = ( props ) => { const { data , languagesSupport } = props ; const { columnDefs } = data ; const { on , off , fire } = useGridEventBus ( ) ; const wrapperRef = useRef < HTMLDivElement | null > ( null ) ; const fixTableRef = useRef < HTMLDivElement | null > ( null ) ; const dataTableRef = useRef < HTMLDivElement | null > ( null ) ; const selectionRef = useRef < SelectionRef | null > ( null ) ; const [ rowNoColumnWidth ] = useState ( ROW_NO_WIDTH ) ; const [ behavior , setBehavior ] = useState < Behavior > ( Behavior . NONE ) ; const [ pickedColumn , setPickedColumn ] = useState < PickedColumn | null > ( null ) ; const forceUpdate = useForceUpdate ( ) ; const arrangeFixedTableStyle = useDecorateFixStyle ( { fixTableRef , dataTableRef } ) ; useEffect ( ( ) => { const onColumnWidthCompress = ( ) => { columnDefs . fixed . forEach ( c => c . width = MIN_COLUMN_WIDTH ) ; columnDefs . data . forEach ( c => c . width = MIN_COLUMN_WIDTH ) ; fire ( GridEventTypes . REPAINT_SELECTION ) ; forceUpdate ( ) ; } ; on ( GridEventTypes . COMPRESS_COLUMN_WIDTH , onColumnWidthCompress ) ; return ( ) => { off ( GridEventTypes . COMPRESS_COLUMN_WIDTH , onColumnWidthCompress ) ; } ; } , [ on , off , fire , columnDefs . fixed , columnDefs . data , forceUpdate ] ) ; const manageCursor = ( options ) => { const { table } = options ; if ( ! table ) { return ; } const { mouseClientX , mouseClientY , avoidResize } = options ; computeAndSetCursor ( { table , mouseClientX , mouseClientY , isFixTable : table === fixTableRef . current , rowNoColumnWidth , displayColumns : table === fixTableRef . current ? columnDefs . fixed : columnDefs . data , fixColumns : columnDefs . fixed , changeBehavior : avoidResize ? ( state ) => { setBehavior ( state === Behavior . CAN_RESIZE ? Behavior . PICK_COLUMN : state ) ; } : setBehavior } ) ; } ; const resizeColumn = ( mouseClientX ) => { if ( ! pickedColumn ) { return ; } const { left : wrapperLeft } = wrapperRef . current ! . getBoundingClientRect ( ) ; const movementX = mouseClientX - wrapperLeft - pickedColumn . offsetX ; pickedColumn . column . width = Math . min ( Math . max ( MIN_COLUMN_WIDTH , pickedColumn . originalWidth + movementX ) , MAX_COLUMN_WIDTH ) ; const isFixTable = columnDefs . fixed . includes ( pickedColumn . column ) ; const table = isFixTable ? fixTableRef . current ! : dataTableRef . current ! ; const columns = isFixTable ? columnDefs . fixed : columnDefs . data ; const header = table . querySelector ( 'div[data-widget="dataset-grid-header"]' ) ! as HTMLDivElement ; const body = table . querySelector ( 'div[data-widget="dataset-grid-body"]' ) ! as HTMLDivElement ; const gridTemplateColumns = header . style . gridTemplateColumns . split ( ' ' ) ; gridTemplateColumns [ ( columns . indexOf ( pickedColumn . column ) + ( isFixTable ? 1 : 0 ) ) * 2 + 1 ] = `${ pickedColumn . column . width }px` ; const newGridTemplateColumns = gridTemplateColumns . join ( ' ' ) ; header . style . gridTemplateColumns = newGridTemplateColumns ; body . style . gridTemplateColumns = newGridTemplateColumns ; if ( isFixTable ) { const dataTable = dataTableRef . current ! ; const left = gridTemplateColumns . map ( width => parseInt ( width ) ) . reduce ( ( total , width ) => total + width , 0 ) ; dataTable . style . left = `${ left }px` ; dataTable . style . position = 'absolute' ; dataTable . style . width = `calc(100% - ${ left }px)` ; } fire ( GridEventTypes . REPAINT_SELECTION ) ; arrangeFixedTableStyle ( ) ; } ; const resetGridTemplateColumns = ( options ) => { const { element , columns , isFixTable } = options ; const gridColumnCount = columns . length * 2 ; const shouldReset = ( index ) => isFixTable ? ( index > 1 && index < gridColumnCount + 3 ) : ( index < gridColumnCount + 1 ) ; const getDefIndex = ( index ) => isFixTable ? ( ( index - 3 ) / 2 ) : ( ( index - 1 ) / 2 ) ; return element . style . gridTemplateColumns . split ( ' ' ) . map ( ( width , index ) => { if ( shouldReset ( index ) ) { return ( index % 2 === 0 ) ? '0px' : `${ columns [ getDefIndex ( index ) ] . width }px` ; } else { return width ; } } ) ; } ; const repaintWhenDragging = ( options ) => { const { mouseClientX , sourceTable , sourceColumns , targetTable , targetColumns , pickedColumn } = options ; const dragColumnIndex = sourceColumns . indexOf ( pickedColumn ) ; const sourceHeader = sourceTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-header"]' ) ! ; const sourceBody = sourceTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-body"]' ) ! ; const sourceIsFixTable = sourceTable === fixTableRef . current ; const sourceGridTemplateColumns = resetGridTemplateColumns ( { element : sourceHeader , columns : sourceColumns , isFixTable : sourceIsFixTable } ) ; if ( sourceIsFixTable ) { sourceGridTemplateColumns [ ( dragColumnIndex + 1 ) * 2 + 1 ] = '0' ; } else { sourceGridTemplateColumns [ ( dragColumnIndex + 1 ) * 2 - 1 ] = '0' ; } const targetIsFixTable = targetTable === fixTableRef . current ; const targetColumnsWidths = computeTableColumnsRightPositions ( targetColumns ) ; const { left : targetTableLeft } = targetTable . getBoundingClientRect ( ) ; const targetScrollLeft = targetTable . scrollLeft ; const targetColumnIndex = targetColumnsWidths . findIndex ( width => { return width - targetScrollLeft > mouseClientX - targetTableLeft - ( targetIsFixTable ? rowNoColumnWidth : 0 ) ; } ) ; let targetHeader , targetBody , targetGridTemplateColumns ; if ( sourceTable === targetTable ) { targetHeader = sourceHeader ; targetBody = sourceBody ; targetGridTemplateColumns = sourceGridTemplateColumns ; } else { targetHeader = targetTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-header"]' ) ! ; targetBody = targetTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-body"]' ) ! ; targetGridTemplateColumns = resetGridTemplateColumns ( { element : targetHeader , columns : targetColumns , isFixTable : targetIsFixTable } ) ; } if ( targetColumnIndex === - 1 ) { if ( targetIsFixTable ) { targetGridTemplateColumns [ ( targetColumns . length + 1 ) * 2 ] = `${ pickedColumn . width }px` ; } else { targetGridTemplateColumns [ targetColumns . length * 2 + 1 ] = `${ pickedColumn . width }px` ; } } else { const targetColumnRightX = targetColumnsWidths [ targetColumnIndex ] ; const targetColumnLeftX = targetColumnRightX - targetColumns [ targetColumnIndex ] . width ; const targetColumnCenterX = ( targetColumnRightX + targetColumnLeftX ) / 2 ; if ( mouseClientX - targetTableLeft > targetColumnCenterX - targetScrollLeft ) { targetGridTemplateColumns [ ( targetColumnIndex + 1 ) * 2 + ( targetIsFixTable ? 2 : 0 ) ] = `${ pickedColumn . width }px` ; } else { targetGridTemplateColumns [ ( targetColumnIndex + 1 ) * 2 + ( targetIsFixTable ? 2 : 0 ) - 2 ] = `${ pickedColumn . width }px` ; } } const isTableInside = sourceTable === targetTable ; if ( ! isTableInside ) { const newSourceGridTemplateColumns = sourceGridTemplateColumns . join ( ' ' ) ; sourceHeader . style . gridTemplateColumns = newSourceGridTemplateColumns ; sourceBody . style . gridTemplateColumns = newSourceGridTemplateColumns ; } const newTargetGridTemplateColumns = targetGridTemplateColumns . join ( ' ' ) ; targetHeader . style . gridTemplateColumns = newTargetGridTemplateColumns ; targetBody . style . gridTemplateColumns = newTargetGridTemplateColumns ; let fixTableWidth ; let fixTable ; let dataTable ; if ( sourceIsFixTable ) { fixTableWidth = sourceGridTemplateColumns . map ( width => parseInt ( width ) ) . reduce ( ( total , width ) => total + width , 0 ) ; if ( isTableInside ) { fixTable = sourceTable ; dataTable = dataTableRef . current ! ; const dataTableHeader = dataTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-header"]' ) ! ; const dataTableBody = dataTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-body"]' ) ! ; const dataTableGridColumns = `0 ${ columnDefs . data . map ( column => `${ column . width }px 0` ) . join ( ' ' ) } minmax(${ FILLER_MIN_WIDTH }px, 1fr)` ; dataTableHeader . style . gridTemplateColumns = dataTableGridColumns ; dataTableBody . style . gridTemplateColumns = dataTableGridColumns ; } else { fixTable = sourceTable ; dataTable = targetTable ; } } else { if ( isTableInside ) { fixTable = fixTableRef . current ! ; const fixTableHeader = fixTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-header"]' ) ! ; const fixTableBody = fixTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-body"]' ) ! ; const fixTableGridColumns = `0 ${ rowNoColumnWidth }px 0 ${ columnDefs . fixed . map ( column => `${ column . width }px 0` ) . join ( ' ' ) }` ; fixTableHeader . style . gridTemplateColumns = fixTableGridColumns ; fixTableBody . style . gridTemplateColumns = fixTableGridColumns ; fixTableWidth = columnDefs . fixed . reduce ( ( width , column ) => width + column . width , rowNoColumnWidth ) ; dataTable = sourceTable ; } else { fixTableWidth = targetGridTemplateColumns . map ( width => parseInt ( width ) ) . reduce ( ( total , width ) => total + width , 0 ) ; fixTable = targetTable ; dataTable = sourceTable ; } } fixTable . style . minWidth = 'unset' ; fixTable . style . width = `${ fixTableWidth }px` ; dataTable . style . left = `${ fixTableWidth }px` ; dataTable . style . position = 'absolute' ; dataTable . style . width = `calc(100% - ${ fixTableWidth }px)` ; } ; const computeSourceAndTarget = ( mouseClientX , column ) => { const dataTable = dataTableRef . current ! ; const fixTable = fixTableRef . current ! ; const dataTableLeft = dataTable . getBoundingClientRect ( ) . left ; let fromFixTable , toFixTable , sourceTable , sourceColumns , targetTable , targetColumns ; if ( columnDefs . data . includes ( column ) ) { fromFixTable = false ; sourceTable = dataTable ; sourceColumns = columnDefs . data ; } else { fromFixTable = true ; sourceTable = fixTable ; sourceColumns = columnDefs . fixed ; } if ( mouseClientX >= dataTableLeft ) { toFixTable = false ; targetTable = dataTable ; targetColumns = columnDefs . data ; } else { toFixTable = true ; targetTable = fixTable ; targetColumns = columnDefs . fixed ; } return { fromFixTable , toFixTable , sourceTable , sourceColumns , targetTable , targetColumns } ; } ; const dragColumn = async ( mouseClientX ) => { if ( ! pickedColumn ) { return ; } const wrapper = wrapperRef . current ! ; const { left : wrapperLeft } = wrapper . getBoundingClientRect ( ) ; const movementX = mouseClientX - wrapperLeft - pickedColumn . offsetX ; fire ( GridEventTypes . ASK_DRAG_COLUMN_VISIBLE , ( dragColumnVisible ) => { if ( ! dragColumnVisible && Math . abs ( movementX ) <= DRAG_DEVIATION ) { return ; } window . getSelection ( ) ?. removeAllRanges ( ) ; fire ( GridEventTypes . DRAG_COLUMN_STATE_CHANGED , { left : mouseClientX - wrapperLeft - pickedColumn . column . width / 2 , movementX } ) ; if ( behavior !== Behavior . DRAGGING ) { const selection = selectionRef . current ! . selection ( ) ; fire ( GridEventTypes . SELECTION_CHANGED , selection . inFixTable , selection . row , - 1 ) ; setBehavior ( Behavior . DRAGGING ) ; } if ( ! dragColumnVisible ) { fire ( GridEventTypes . DRAG_COLUMN_VISIBLE_CHANGED , true ) ; } const { sourceTable , sourceColumns , targetTable , targetColumns } = computeSourceAndTarget ( mouseClientX , pickedColumn . column ) ; repaintWhenDragging ( { mouseClientX , sourceTable , sourceColumns , targetTable , targetColumns , pickedColumn : pickedColumn . column } ) ; } ) ; } ; const onMouseMove = ( event ) => { if ( behavior === Behavior . RESIZING && pickedColumn ) { resizeColumn ( event . clientX ) ; } else if ( ( behavior === Behavior . READY_TO_DRAG || behavior === Behavior . DRAGGING ) && pickedColumn ) { dragColumn ( event . clientX ) ; } else { manageCursor ( { table : findDataTable ( event . target as HTMLElement ) , mouseClientX : event . clientX , mouseClientY : event . clientY , avoidResize : false } ) ; } } ; const prepareToDragColumn = ( table , mouseClientX ) => { const isFixTable = table === fixTableRef . current ; const wrapperLeft = wrapperRef . current ! . getBoundingClientRect ( ) . left ; const pickedColumn = findPickedColumnForDrag ( { wrapperLeft , table , mouseClientX , columnDefs , isFixTable , rowNoColumnWidth } ) ; setPickedColumn ( pickedColumn ) ; const { scrollTop } = table ; const height = Math . min ( table . clientHeight , HEADER_HEIGHT + data . data . length * ROW_HEIGHT ) ; const startRowIndex = Math . floor ( scrollTop / ROW_HEIGHT ) ; const endRowIndex = startRowIndex + Math . ceil ( ( height - HEADER_HEIGHT ) / ROW_HEIGHT + 2 ) ; fire ( GridEventTypes . DRAG_COLUMN_STATE_CHANGED , { height , startRowIndex , endRowIndex , firstRowOffsetY : scrollTop % ROW_HEIGHT , movementX : 0 } ) ; setBehavior ( Behavior . READY_TO_DRAG ) ; } ; const prepareToResizeColumn = ( table , mouseClientX ) => { setPickedColumn ( findPickedColumnForResize ( { wrapperLeft : wrapperRef . current ! . getBoundingClientRect ( ) . left , table , mouseClientX , columnDefs , isFixTable : table === fixTableRef . current , rowNoColumnWidth } ) ) ; setBehavior ( Behavior . RESIZING ) ; } ; const onMouseDown = ( event ) => { if ( behavior === Behavior . CAN_RESIZE ) { prepareToResizeColumn ( findDataTable ( event . target as HTMLElement ) ! , event . clientX ) ; } else if ( behavior === Behavior . PICK_COLUMN ) { prepareToDragColumn ( findDataTable ( event . target as HTMLElement ) ! , event . clientX ) ; } } ; const releaseDragColumn = ( mouseClientX ) => { if ( ! pickedColumn ) { return ; } const dragColumn = pickedColumn . column ; const { fromFixTable , toFixTable } = computeSourceAndTarget ( mouseClientX , dragColumn ) ; let sourceColumnIndex ; if ( fromFixTable ) { sourceColumnIndex = columnDefs . fixed . indexOf ( dragColumn ) ; columnDefs . fixed . splice ( sourceColumnIndex , 1 ) ; } else { sourceColumnIndex = columnDefs . data . indexOf ( dragColumn ) ; columnDefs . data . splice ( sourceColumnIndex , 1 ) ; } const fixTable = fixTableRef . current ! ; const dataTable = dataTableRef . current ! ; let targetColumnIndex ; if ( toFixTable ) { const fixTableHeader = fixTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-header"]' ) ! ; const fixTableBody = fixTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-body"]' ) ! ; const placeholderWidths = fixTableHeader . style . gridTemplateColumns . split ( ' ' ) . filter ( ( width , index ) => index >= 2 && index % 2 === 0 ) . map ( width => parseInt ( width ) ) ; const placeholderIndex = placeholderWidths . findIndex ( width => width !== 0 ) ; if ( fromFixTable ) { if ( placeholderIndex === sourceColumnIndex || placeholderIndex - 1 === sourceColumnIndex ) { targetColumnIndex = sourceColumnIndex ; } else if ( placeholderIndex < sourceColumnIndex ) { targetColumnIndex = placeholderIndex ; } else { targetColumnIndex = placeholderIndex - 1 ; } } else { targetColumnIndex = placeholderIndex ; } columnDefs . fixed . splice ( targetColumnIndex , 0 , dragColumn ) ; const fixTableGridColumns = `0 ${ rowNoColumnWidth }px 0 ${ columnDefs . fixed . map ( column => `${ column . width }px 0` ) . join ( ' ' ) }` ; fixTableHeader . style . gridTemplateColumns = fixTableGridColumns ; fixTableBody . style . gridTemplateColumns = fixTableGridColumns ; } else { const dataTableHeader = dataTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-header"]' ) ! ; const dataTableBody = dataTable . querySelector < HTMLDivElement > ( 'div[data-widget="dataset-grid-body"]' ) ! ; const placeholderWidths = dataTableHeader . style . gridTemplateColumns . split ( ' ' ) . filter ( ( width , index ) => index % 2 === 0 ) . map ( width => parseInt ( width ) ) ; const placeholderIndex = placeholderWidths . findIndex ( width => width !== 0 ) ; if ( fromFixTable ) { targetColumnIndex = placeholderIndex ; } else { if ( placeholderIndex === sourceColumnIndex || placeholderIndex - 1 === sourceColumnIndex ) { targetColumnIndex = sourceColumnIndex ; } else if ( placeholderIndex < sourceColumnIndex ) { targetColumnIndex = placeholderIndex ; } else { targetColumnIndex = placeholderIndex - 1 ; } } columnDefs . data . splice ( targetColumnIndex , 0 , dragColumn ) ; const dataTableGridColumns = `0 ${ columnDefs . data . map ( column => `${ column . width }px 0` ) . join ( ' ' ) } minmax(${ FILLER_MIN_WIDTH }px, 1fr)` ; dataTableHeader . style . gridTemplateColumns = dataTableGridColumns ; dataTableBody . style . gridTemplateColumns = dataTableGridColumns ; } fixTable . style . minWidth = `${ columnDefs . fixed . reduce ( ( width , column ) => width + column . width , rowNoColumnWidth ) }px` ; fixTable . style . width = '' ; dataTable . style . left = '' ; dataTable . style . position = '' ; dataTable . style . width = '' ; setPickedColumn ( null ) ; fire ( GridEventTypes . DRAG_COLUMN_VISIBLE_CHANGED , false ) ; setBehavior ( Behavior . NONE ) ; const selection = selectionRef . current ! . selection ( ) ; fire ( GridEventTypes . SELECTION_CHANGED , toFixTable , selection . row , targetColumnIndex ) ; } ; const onMouseUp = ( event ) => { if ( behavior === Behavior . RESIZING ) { const dataTable = dataTableRef . current ! ; dataTable . style . left = '' ; dataTable . style . position = '' ; dataTable . style . width = '' ; const resizeTable = columnDefs . fixed . some ( c => c === pickedColumn ?. column ) ? fixTableRef . current : dataTableRef . current ; setPickedColumn ( null ) ; manageCursor ( { table : resizeTable , mouseClientX : event . clientX , mouseClientY : event . clientY , avoidResize : true } ) ; } else if ( behavior === Behavior . READY_TO_DRAG ) { setPickedColumn ( null ) ; fire ( GridEventTypes . DRAG_COLUMN_VISIBLE_CHANGED , false ) ; setBehavior ( Behavior . PICK_COLUMN ) ; } else if ( behavior === Behavior . DRAGGING ) { releaseDragColumn ( event . clientX ) ; } } ; const onMouseLeave = ( event ) => { if ( behavior === Behavior . RESIZING ) { const dataTable = dataTableRef . current ! ; dataTable . style . left = '' ; dataTable . style . position = '' ; dataTable . style . width = '' ; setPickedColumn ( null ) ; setBehavior ( Behavior . NONE ) ; } else if ( behavior === Behavior . READY_TO_DRAG ) { setPickedColumn ( null ) ; fire ( GridEventTypes . DRAG_COLUMN_VISIBLE_CHANGED , false ) ; setBehavior ( Behavior . PICK_COLUMN ) ; } else if ( behavior === Behavior . DRAGGING ) { releaseDragColumn ( event . clientX ) ; } } ; const onColumnFixChange = ( column , fix ) => { if ( fix ) { const index = columnDefs . data . indexOf ( column ) ; columnDefs . fixed = [ ... columnDefs . fixed , ... columnDefs . data . splice ( 0 , index + 1 ) ] ; fire ( GridEventTypes . FIX_COLUMN_CHANGED , fix , index + 1 ) ; forceUpdate ( ) ; } else { const index = columnDefs . fixed . indexOf ( column ) ; const count = columnDefs . fixed . length - index ; columnDefs . data = [ ... columnDefs . fixed . splice ( index ) , ... columnDefs . data ] ; fire ( GridEventTypes . FIX_COLUMN_CHANGED , fix , count ) ; forceUpdate ( ) ; } } ; const onColumnSort = ( column , asc ) => { const index = column . index ; if ( asc && column . sort === ColumnSortBy . ASC ) { return ; } if ( ! asc && column . sort === ColumnSortBy . DESC ) { return ; } data . data . sort ( ( r1 , r2 ) => { let ret ; const v1 = r1 [ index ] ; const v2 = r2 [ index ] ; if ( v1 == null ) { ret = - 1 ; } else if ( v2 == null ) { ret = 1 ; } else if ( typeof v1 === 'number' && typeof v2 === 'number' ) { ret = v1 - v2 ; } else if ( typeof v1 === 'boolean' && typeof v2 === 'boolean' ) { ret = ! v1 ? - 1 : ( ! v2 ? 1 : 0 ) ; } else { ret = `${ v1 }` . toUpperCase ( ) . localeCompare ( `${ v2 }` . toUpperCase ( ) ) ; } return asc ? ret : ret * - 1 ; } ) ; column . sort = asc ? ColumnSortBy . ASC : ColumnSortBy . DESC ; columnDefs . fixed . filter ( c => c !== column ) . forEach ( c => c . sort = ColumnSortBy . NONE ) ; columnDefs . data . filter ( c => c !== column ) . forEach ( c => c . sort = ColumnSortBy . NONE ) ; forceUpdate ( ) ; } ; return < GridWrapperContainer data - resize - state = { behavior } onMouseMove = { onMouseMove } onMouseDown = { onMouseDown } onMouseUp = { onMouseUp } onMouseLeave = { onMouseLeave } ref = { wrapperRef } > < Grid displayColumns = { columnDefs . fixed } isFixTable = { true } rowNoColumnWidth = { rowNoColumnWidth } data = { data } onColumnFixChange = { onColumnFixChange } onColumnSort = { onColumnSort } dragColumn = { behavior === Behavior . DRAGGING ? pickedColumn ?. column : ( void 0 ) } languagesSupport = { languagesSupport } ref = { fixTableRef } / > < Grid displayColumns = { columnDefs . data } isFixTable = { false } rowNoColumnWidth = { rowNoColumnWidth } data = { data } onColumnFixChange = { onColumnFixChange } onColumnSort = { onColumnSort } dragColumn = { behavior === Behavior . DRAGGING ? pickedColumn ?. column : ( void 0 ) } languagesSupport = { languagesSupport } ref = { dataTableRef } / > < GridSelection data = { data } columnDefs = { columnDefs } rowNoColumnWidth = { rowNoColumnWidth } dataTableRef = { dataTableRef } ref = { selectionRef } / > < GridResizeShade visible = { behavior === Behavior . RESIZING || behavior === Behavior . DRAGGING } /> < GridDragColumn data = { data } column = { pickedColumn ?. column } / > < GridScrollShade wrapperRef = { wrapperRef } dataTableRef = { dataTableRef } visible = { behavior === Behavior . DRAGGING } /> < /GridWrapperContainer>; } ;