diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx b/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx index f67e27bcea6..8c630c2eba0 100644 --- a/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx +++ b/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx @@ -2097,8 +2097,21 @@ describe('AnalyticalTable', () => { getData: () => { return colId; }, + types: ['text', 'application/x-ui5wcr-columndnd'], }); + // only real column drags may highlight a header. + const borderSide = dir === 'rtl' ? 'border-right-width' : 'border-left-width'; + // Foreign (file) drag must NOT highlight the header. + cy.get('[data-column-id="age"]').trigger('dragenter', { dataTransfer: { getData: () => '', types: ['Files'] } }); + cy.get('[data-column-id="age"]').should('have.css', borderSide, '0px'); + // A real column drag highlights the header it enters... + cy.get('[data-column-id="age"]').trigger('dragenter', { dataTransfer: dataTransfereById('name') }); + cy.get('[data-column-id="age"]').should('have.css', borderSide, '3px'); + // ...and leaving the header (relatedTarget outside) clears the highlight again. + cy.get('[data-column-id="age"]').trigger('dragleave', { relatedTarget: null }); + cy.get('[data-column-id="age"]').should('have.css', borderSide, '0px'); + cy.get('[data-column-id="name"]') .trigger('dragstart') .trigger('drop', { dataTransfer: dataTransfereById('age') }); @@ -2137,6 +2150,7 @@ describe('AnalyticalTable', () => { const dataTransferById = (colId) => ({ getData: () => colId, + types: ['text', 'application/x-ui5wcr-columndnd'], }); cy.mount(); diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx b/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx index c4f1d57f57e..56cd5f7757d 100644 --- a/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx +++ b/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx @@ -27,6 +27,7 @@ export interface ColumnHeaderProps { onDragOver: DragEventHandler; onDrop: DragEventHandler; onDragEnter: DragEventHandler; + onDragLeave: DragEventHandler; onDragEnd: DragEventHandler; dragOver: boolean; isDraggable: boolean; @@ -67,6 +68,7 @@ export const ColumnHeader = (props: ColumnHeaderProps) => { onDragOver, onDragStart, onDrop, + onDragLeave, onDragEnd, headerTooltip, isDraggable, @@ -211,6 +213,7 @@ export const ColumnHeader = (props: ColumnHeaderProps) => { onDragOver={onDragOver} onDragStart={onDragStart} onDrop={onDrop} + onDragLeave={onDragLeave} onDragEnd={onDragEnd} data-column-id={columnId} onClick={handleHeaderCellClick} diff --git a/packages/main/src/components/AnalyticalTable/hooks/useDragAndDrop.ts b/packages/main/src/components/AnalyticalTable/hooks/useDragAndDrop.ts index c12fd1cee1d..11e0e43272c 100644 --- a/packages/main/src/components/AnalyticalTable/hooks/useDragAndDrop.ts +++ b/packages/main/src/components/AnalyticalTable/hooks/useDragAndDrop.ts @@ -1,6 +1,10 @@ import { enrichEventWithDetails } from '@ui5/webcomponents-react-base'; import type { ColumnType, ReactTableHooks, TableInstance } from '../types/index.js'; +// Custom drag data type set on column drags so they can be distinguished from foreign drags (e.g. files, text). +const COLUMN_DND_TYPE = 'application/x-ui5wcr-columndnd'; +const isColumnDrag = (e) => !!e.dataTransfer?.types?.includes(COLUMN_DND_TYPE); + function getHeaderProps( props: Record, { instance: { dispatch, state, columns, setColumnOrder, webComponentsReactProperties } }: { instance: TableInstance }, @@ -14,16 +18,31 @@ function getHeaderProps( return; } e.dataTransfer.setData('text', e.currentTarget.dataset.columnId); + e.dataTransfer.setData(COLUMN_DND_TYPE, ''); }; const handleDragOver = (e) => { + if (!isColumnDrag(e)) { + return; + } e.preventDefault(); }; const handleDragEnter = (e) => { + if (!isColumnDrag(e)) { + return; + } dispatch({ type: 'COLUMN_DND_START', payload: e.currentTarget.dataset.columnId }); }; + const handleDragLeave = (e) => { + // dragleave also fires when moving onto a child element; ignore those to avoid clearing the highlight prematurely. + if (e.currentTarget.contains(e.relatedTarget)) { + return; + } + dispatch({ type: 'COLUMN_DND_END' }); + }; + const handleOnDragEnd = () => { dispatch({ type: 'COLUMN_DND_END' }); }; @@ -31,6 +50,10 @@ function getHeaderProps( const handleOnDrop = (e) => { dispatch({ type: 'COLUMN_DND_END' }); + if (!isColumnDrag(e)) { + return; + } + const droppedColId = e.currentTarget.dataset.columnId; const draggedColId = e.dataTransfer.getData('text'); if (droppedColId === draggedColId) return; @@ -74,6 +97,7 @@ function getHeaderProps( onDragStart: handleDragStart, onDragEnter: handleDragEnter, onDragOver: handleDragOver, + onDragLeave: handleDragLeave, onDragEnd: handleOnDragEnd, onDrop: handleOnDrop, dragOver: dndColumn === props.id,