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,