diff --git a/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid.scss b/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid.scss
index 8ca532f190..47bbd99c13 100644
--- a/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid.scss
+++ b/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid.scss
@@ -131,6 +131,38 @@ $root: ".widget-datagrid";
align-self: center;
}
+ /* Drag handle */
+ .drag-handle {
+ cursor: grab;
+ pointer-events: auto;
+ position: relative;
+ width: 14px;
+ padding: 0;
+ flex-grow: 0;
+ flex-shrink: 0;
+ display: flex;
+ justify-content: center;
+ align-self: normal;
+ z-index: 1;
+
+ &:hover {
+ background-color: var(--brand-primary-50, $brand-light);
+ svg {
+ color: var(--brand-primary, $brand-primary);
+ }
+ }
+ &:active {
+ cursor: grabbing;
+ }
+ svg {
+ margin: 0;
+ }
+ }
+
+ .drag-handle + .column-caption {
+ padding-inline-start: 4px;
+ }
+
&:focus:not(:focus-visible) {
outline: none;
}
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js
index 8a390bba23..5f301156b9 100644
--- a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js
+++ b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js
@@ -49,10 +49,9 @@ test.describe("capabilities: sorting", () => {
await page.goto("/");
await page.waitForLoadState("networkidle");
await expect(page.locator(".mx-name-datagrid1 .column-header").nth(1)).toHaveText("First Name");
- await expect(page.locator(".mx-name-datagrid1 .column-header").nth(1).locator("svg")).toHaveAttribute(
- "data-icon",
- "arrows-alt-v"
- );
+ await expect(
+ page.locator(".mx-name-datagrid1 .column-header").nth(1).locator("svg[data-icon='arrows-alt-v']")
+ ).toBeVisible();
await expect(page.getByRole("gridcell", { name: "12" }).first()).toHaveText("12");
});
@@ -60,15 +59,13 @@ test.describe("capabilities: sorting", () => {
await page.goto("/");
await page.waitForLoadState("networkidle");
await expect(page.locator(".mx-name-datagrid1 .column-header").nth(1)).toHaveText("First Name");
- await expect(page.locator(".mx-name-datagrid1 .column-header").nth(1).locator("svg")).toHaveAttribute(
- "data-icon",
- "arrows-alt-v"
- );
+ await expect(
+ page.locator(".mx-name-datagrid1 .column-header").nth(1).locator("svg[data-icon='arrows-alt-v']")
+ ).toBeVisible();
await page.locator(".mx-name-datagrid1 .column-header").nth(1).click();
- await expect(page.locator(".mx-name-datagrid1 .column-header").nth(1).locator("svg")).toHaveAttribute(
- "data-icon",
- "long-arrow-alt-up"
- );
+ await expect(
+ page.locator(".mx-name-datagrid1 .column-header").nth(1).locator("svg[data-icon='long-arrow-alt-up']")
+ ).toBeVisible();
await expect(page.getByRole("gridcell", { name: "10" }).first()).toHaveText("10");
});
@@ -78,10 +75,9 @@ test.describe("capabilities: sorting", () => {
await expect(page.locator(".mx-name-datagrid1 .column-header").nth(1)).toHaveText("First Name");
await page.locator(".mx-name-datagrid1 .column-header").nth(1).click();
await page.locator(".mx-name-datagrid1 .column-header").nth(1).click();
- await expect(page.locator(".mx-name-datagrid1 .column-header").nth(1).locator("svg")).toHaveAttribute(
- "data-icon",
- "long-arrow-alt-down"
- );
+ await expect(
+ page.locator(".mx-name-datagrid1 .column-header").nth(1).locator("svg[data-icon='long-arrow-alt-down']")
+ ).toBeVisible();
await expect(page.getByRole("gridcell", { name: "12" }).first()).toHaveText("12");
});
});
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/dataGridColumnContent-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/dataGridColumnContent-chromium-linux.png
index 7c5d4f9ad5..b97a70af7e 100644
Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/dataGridColumnContent-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/dataGridColumnContent-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-chromium-linux.png
index 38f3b9083b..fe5dfd43d7 100644
Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-virtual-scrolling-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-virtual-scrolling-chromium-linux.png
index 45e2db5267..52edfa49d7 100644
Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-virtual-scrolling-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js-snapshots/datagrid-virtual-scrolling-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionCheckbox-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionCheckbox-chromium-linux.png
index cfee3848f6..c31c9026b2 100644
Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionCheckbox-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionCheckbox-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionRowClick-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionRowClick-chromium-linux.png
index 8c3e964ecd..e45c44b4d2 100644
Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionRowClick-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridMultiSelectionRowClick-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionCheckbox-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionCheckbox-chromium-linux.png
index fb2437d04c..682b957004 100644
Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionCheckbox-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionCheckbox-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionRowClick-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionRowClick-chromium-linux.png
index 2e2f7dce38..e7805d2ff2 100644
Binary files a/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionRowClick-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/DataGridSelection.spec.js-snapshots/datagridSingleSelectionRowClick-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringIntegration.spec.js-snapshots/datagridFilteringIntegration-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringIntegration.spec.js-snapshots/datagridFilteringIntegration-chromium-linux.png
index 4977aebf8f..77e1735dd5 100644
Binary files a/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringIntegration.spec.js-snapshots/datagridFilteringIntegration-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringIntegration.spec.js-snapshots/datagridFilteringIntegration-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringSingle.spec.js-snapshots/datagridFilteringSingle-chromium-linux.png b/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringSingle.spec.js-snapshots/datagridFilteringSingle-chromium-linux.png
index db600cfde2..32bc7393e2 100644
Binary files a/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringSingle.spec.js-snapshots/datagridFilteringSingle-chromium-linux.png and b/packages/pluggableWidgets/datagrid-web/e2e/filtering/DataGridFilteringSingle.spec.js-snapshots/datagridFilteringSingle-chromium-linux.png differ
diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx
index 7d666397a3..073ffa5880 100644
--- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx
@@ -4,9 +4,11 @@ import { GUID, ObjectItem } from "mendix";
import { Selectable } from "mendix/preview/Selectable";
import { createContext, CSSProperties, PropsWithChildren, ReactElement, ReactNode, useContext } from "react";
import { ColumnsPreviewType, DatagridPreviewProps } from "typings/DatagridProps";
+import { DragHandle } from "./components/DragHandle";
import { FaArrowsAltV } from "./components/icons/FaArrowsAltV";
import { FaEye } from "./components/icons/FaEye";
import { ColumnPreview } from "./helpers/ColumnPreview";
+
import "./ui/DatagridPreview.scss";
declare module "mendix/preview/Selectable" {
@@ -157,7 +159,7 @@ function GridHeader(): ReactNode {
}
function ColumnHeader({ column }: { column: ColumnsPreviewType }): ReactNode {
- const { columnsFilterable, columnsSortable, columnsHidable } = useProps();
+ const { columnsFilterable, columnsSortable, columnsHidable, columnsDraggable } = useProps();
const columnPreview = new ColumnPreview(column, 0);
const caption = columnPreview.header;
const canSort = columnsSortable && columnPreview.canSort;
@@ -172,6 +174,9 @@ function ColumnHeader({ column }: { column: ColumnsPreviewType }): ReactNode {
>
+ {columnsDraggable && (
+ {}} onDragEnd={() => {}} />
+ )}
{caption.length > 0 ? caption : "\u00a0"}
{canSort && }
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/ColumnContainer.tsx b/packages/pluggableWidgets/datagrid-web/src/components/ColumnContainer.tsx
new file mode 100644
index 0000000000..cd08a6aa89
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/components/ColumnContainer.tsx
@@ -0,0 +1,64 @@
+import classNames from "classnames";
+import { ReactElement } from "react";
+import { ColumnHeader } from "./ColumnHeader";
+import { useColumn, useColumnsStore, useDatagridConfig, useHeaderDragnDropVM } from "../model/hooks/injection-hooks";
+import { ColumnResizerProps } from "./ColumnResizer";
+import { observer } from "mobx-react-lite";
+
+export interface ColumnContainerProps {
+ isLast?: boolean;
+ resizer: ReactElement
;
+}
+
+export const ColumnContainer = observer(function ColumnContainer(props: ColumnContainerProps): ReactElement {
+ const { columnsFilterable, id: gridId } = useDatagridConfig();
+ const { columnFilters } = useColumnsStore();
+ const column = useColumn();
+ const { canSort, columnId, columnIndex, canResize, sortDir, header } = column;
+ const vm = useHeaderDragnDropVM();
+ const caption = header.trim();
+
+ return (
+ column.setHeaderElementRef(ref)}
+ data-column-id={columnId}
+ onDrop={vm.handleOnDrop}
+ onDragEnter={vm.handleDragEnter}
+ onDragOver={vm.handleDragOver}
+ >
+
+
+ {columnsFilterable && (
+
+ {columnFilters[columnIndex]?.renderFilterWidgets()}
+
+ )}
+
+ {canResize ? props.resizer : null}
+
+ );
+});
+
+function getAriaSort(canSort: boolean, sortDir: string | undefined): "ascending" | "descending" | "none" | undefined {
+ if (!canSort) {
+ return undefined;
+ }
+
+ switch (sortDir) {
+ case "asc":
+ return "ascending";
+ case "desc":
+ return "descending";
+ default:
+ return "none";
+ }
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/ColumnHeader.tsx b/packages/pluggableWidgets/datagrid-web/src/components/ColumnHeader.tsx
new file mode 100644
index 0000000000..024ab37b12
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/components/ColumnHeader.tsx
@@ -0,0 +1,63 @@
+import classNames from "classnames";
+import { HTMLAttributes, KeyboardEvent, ReactElement, ReactNode } from "react";
+import { DragHandle } from "./DragHandle";
+import { FaArrowsAltV } from "./icons/FaArrowsAltV";
+import { FaLongArrowAltDown } from "./icons/FaLongArrowAltDown";
+import { FaLongArrowAltUp } from "./icons/FaLongArrowAltUp";
+import { useColumn, useHeaderDragnDropVM } from "../model/hooks/injection-hooks";
+import { observer } from "mobx-react-lite";
+import { SortDirection } from "../typings/sorting";
+
+interface SortIconProps {
+ direction: SortDirection | undefined;
+}
+
+export const ColumnHeader = observer(function ColumnHeader(): ReactElement {
+ const column = useColumn();
+ const { header, canSort, alignment } = column;
+ const caption = header.trim();
+ const sortProps = canSort ? getSortProps(() => column.toggleSort()) : null;
+ const vm = useHeaderDragnDropVM();
+
+ return (
+
+ {vm.isDraggable && (
+
+ )}
+ {caption.length > 0 ? caption : "\u00a0"}
+ {canSort ? : null}
+
+ );
+});
+
+function SortIcon({ direction }: SortIconProps): ReactNode {
+ switch (direction) {
+ case "asc":
+ return ;
+ case "desc":
+ return ;
+ default:
+ return ;
+ }
+}
+
+function getSortProps(toggleSort: () => void): HTMLAttributes {
+ return {
+ onClick: () => {
+ toggleSort();
+ },
+ onKeyDown: (e: KeyboardEvent) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ toggleSort();
+ }
+ },
+ role: "button",
+ tabIndex: 0
+ };
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/ColumnResizer.tsx b/packages/pluggableWidgets/datagrid-web/src/components/ColumnResizer.tsx
index 90dc7f7449..bcbc4bcfe9 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/ColumnResizer.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/ColumnResizer.tsx
@@ -1,24 +1,18 @@
import { useEventCallback } from "@mendix/widget-plugin-hooks/useEventCallback";
import { MouseEvent, ReactElement, TouchEvent, useCallback, useEffect, useRef, useState } from "react";
+import { useColumn, useColumnsStore } from "../model/hooks/injection-hooks";
export interface ColumnResizerProps {
minWidth?: number;
- setColumnWidth: (width: number) => void;
- onResizeEnds?: () => void;
- onResizeStart?: () => void;
}
-export function ColumnResizer({
- minWidth = 50,
- setColumnWidth,
- onResizeEnds,
- onResizeStart
-}: ColumnResizerProps): ReactElement {
+export function ColumnResizer({ minWidth = 50 }: ColumnResizerProps): ReactElement {
+ const column = useColumn();
+ const columnsStore = useColumnsStore();
const [isResizing, setIsResizing] = useState(false);
const [startPosition, setStartPosition] = useState(0);
const [currentWidth, setCurrentWidth] = useState(0);
const resizerReference = useRef(null);
- const onStart = useEventCallback(onResizeStart);
const onStartDrag = useCallback(
(e: TouchEvent & MouseEvent): void => {
@@ -26,12 +20,12 @@ export function ColumnResizer({
setStartPosition(mouseX);
setIsResizing(true);
if (resizerReference.current) {
- const column = resizerReference.current.parentElement!;
- setCurrentWidth(column.offsetWidth);
+ const columnElement = resizerReference.current.parentElement!;
+ setCurrentWidth(columnElement.offsetWidth);
}
- onStart();
+ columnsStore.setIsResizing(true);
},
- [onStart]
+ [columnsStore]
);
const onEndDrag = useCallback((): void => {
if (!isResizing) {
@@ -39,9 +33,9 @@ export function ColumnResizer({
}
setIsResizing(false);
setCurrentWidth(0);
- onResizeEnds?.();
- }, [onResizeEnds, isResizing]);
- const setColumnWidthStable = useEventCallback(setColumnWidth);
+ columnsStore.setIsResizing(false);
+ }, [columnsStore, isResizing]);
+ const setColumnWidthStable = useEventCallback((width: number) => column.setSize(width));
const onMouseMove = useCallback(
(e: TouchEvent & MouseEvent & Event): void => {
if (!isResizing) {
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/DragHandle.tsx b/packages/pluggableWidgets/datagrid-web/src/components/DragHandle.tsx
new file mode 100644
index 0000000000..d988dc4b23
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/components/DragHandle.tsx
@@ -0,0 +1,46 @@
+import { DragEvent, DragEventHandler, MouseEvent, ReactElement } from "react";
+import { FaGripVertical } from "./icons/FaGripVertical";
+
+interface DragHandleProps {
+ draggable: boolean;
+ onDragStart?: DragEventHandler;
+ onDragEnd?: DragEventHandler;
+}
+export function DragHandle({ draggable, onDragStart, onDragEnd }: DragHandleProps): ReactElement {
+ const handleMouseDown = (e: MouseEvent): void => {
+ // Only stop propagation, don't prevent default - we need default for drag to work
+ e.stopPropagation();
+ };
+
+ const handleClick = (e: MouseEvent): void => {
+ // Stop click events from bubbling to prevent sorting
+ e.stopPropagation();
+ e.preventDefault();
+ };
+
+ const handleDragStart = (e: DragEvent): void => {
+ // Don't stop propagation here - let the drag start properly
+ if (onDragStart) {
+ onDragStart(e);
+ }
+ };
+
+ const handleDragEnd = (e: DragEvent): void => {
+ if (onDragEnd) {
+ onDragEnd(e);
+ }
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/GridHeader.tsx b/packages/pluggableWidgets/datagrid-web/src/components/GridHeader.tsx
index 39b86c6d3a..029dfc3bae 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/GridHeader.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/GridHeader.tsx
@@ -1,19 +1,16 @@
-import { ReactElement, useState } from "react";
+import { ReactElement } from "react";
import { useColumnsStore, useDatagridConfig } from "../model/hooks/injection-hooks";
-import { ColumnId } from "../typings/GridColumn";
import { CheckboxColumnHeader } from "./CheckboxColumnHeader";
import { ColumnProvider } from "./ColumnProvider";
import { ColumnResizer } from "./ColumnResizer";
import { ColumnSelector } from "./ColumnSelector";
-import { Header } from "./Header";
+import { ColumnContainer } from "./ColumnContainer";
import { HeaderSkeletonLoader } from "./loader/HeaderSkeletonLoader";
export function GridHeader(): ReactElement {
const { columnsHidable, id: gridId } = useDatagridConfig();
const columnsStore = useColumnsStore();
const columns = columnsStore.visibleColumns;
- const [dragOver, setDragOver] = useState<[ColumnId, "before" | "after"] | undefined>(undefined);
- const [isDragging, setIsDragging] = useState<[ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined>();
if (!columnsStore.loaded) {
return ;
@@ -25,19 +22,7 @@ export function GridHeader(): ReactElement {
{columns.map(column => (
- columnsStore.setIsResizing(true)}
- onResizeEnds={() => columnsStore.setIsResizing(false)}
- setColumnWidth={(width: number) => column.setSize(width)}
- />
- }
- setDropTarget={setDragOver}
- setIsDragging={setIsDragging}
- />
+ } />
))}
{columnsHidable && (
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/Header.tsx b/packages/pluggableWidgets/datagrid-web/src/components/Header.tsx
deleted file mode 100644
index ed334d2ad9..0000000000
--- a/packages/pluggableWidgets/datagrid-web/src/components/Header.tsx
+++ /dev/null
@@ -1,239 +0,0 @@
-import classNames from "classnames";
-import {
- Dispatch,
- DragEvent,
- DragEventHandler,
- HTMLAttributes,
- KeyboardEvent,
- ReactElement,
- ReactNode,
- SetStateAction,
- useCallback
-} from "react";
-import { FaArrowsAltV } from "./icons/FaArrowsAltV";
-import { FaLongArrowAltDown } from "./icons/FaLongArrowAltDown";
-import { FaLongArrowAltUp } from "./icons/FaLongArrowAltUp";
-
-import { useColumn, useColumnsStore, useDatagridConfig } from "../model/hooks/injection-hooks";
-import { ColumnId, GridColumn } from "../typings/GridColumn";
-import { ColumnResizerProps } from "./ColumnResizer";
-
-export interface HeaderProps {
- isLast?: boolean;
- resizer: ReactElement;
-
- dropTarget?: [ColumnId, "before" | "after"];
- isDragging?: [ColumnId | undefined, ColumnId, ColumnId | undefined];
- setDropTarget: Dispatch>;
- setIsDragging: Dispatch>;
-}
-
-export function Header(props: HeaderProps): ReactElement {
- const { columnsFilterable, id: gridId, columnsDraggable, columnsResizable, columnsSortable } = useDatagridConfig();
- const columnsStore = useColumnsStore();
- const column = useColumn();
- const canDrag = columnsDraggable && column.canDrag;
- const canSort = columnsSortable && column.canSort;
- const canResize = columnsResizable && column.canResize;
-
- const draggableProps = useDraggable(
- canDrag,
- columnsStore.swapColumns.bind(columnsStore),
- props.dropTarget,
- props.setDropTarget,
- props.isDragging,
- props.setIsDragging
- );
-
- const sortIcon = canSort ? getSortIcon(column) : null;
- const sortProps = canSort ? getSortProps(column) : null;
- const caption = column.header.trim();
-
- return (
- column.setHeaderElementRef(ref)}
- data-column-id={column.columnId}
- onDrop={draggableProps.onDrop}
- onDragEnter={draggableProps.onDragEnter}
- onDragOver={draggableProps.onDragOver}
- >
-
-
- {caption.length > 0 ? caption : "\u00a0"}
- {sortIcon}
-
- {columnsFilterable && (
-
- {columnsStore.columnFilters[column.columnIndex]?.renderFilterWidgets()}
-
- )}
-
- {canResize ? props.resizer : null}
-
- );
-}
-
-function useDraggable(
- columnsDraggable: boolean,
- setColumnOrder: (source: ColumnId, target: [ColumnId, "after" | "before"]) => void,
- dropTarget: [ColumnId, "before" | "after"] | undefined,
- setDropTarget: Dispatch>,
- dragging: [ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined,
- setDragging: Dispatch>
-): {
- draggable?: boolean;
- onDragStart?: DragEventHandler;
- onDragOver?: DragEventHandler;
- onDrop?: DragEventHandler;
- onDragEnter?: DragEventHandler;
- onDragEnd?: DragEventHandler;
-} {
- const handleDragStart = useCallback(
- (e: DragEvent): void => {
- const elt = (e.target as HTMLDivElement).closest(".th") as HTMLDivElement;
- const columnId = elt.dataset.columnId ?? "";
-
- const columnAtTheLeft = (elt.previousElementSibling as HTMLDivElement)?.dataset?.columnId as ColumnId;
- const columnAtTheRight = (elt.nextElementSibling as HTMLDivElement)?.dataset?.columnId as ColumnId;
-
- setDragging([columnAtTheLeft, columnId as ColumnId, columnAtTheRight]);
- },
- [setDragging]
- );
-
- const handleDragOver = useCallback(
- (e: DragEvent): void => {
- if (!dragging) {
- return;
- }
- const columnId = (e.currentTarget as HTMLDivElement).dataset.columnId as ColumnId;
- if (!columnId) {
- return;
- }
- e.preventDefault();
-
- const [leftSiblingColumnId, draggingColumnId, rightSiblingColumnId] = dragging;
-
- if (columnId === draggingColumnId) {
- // hover on itself place, no highlight
- if (dropTarget !== undefined) {
- setDropTarget(undefined);
- }
- return;
- }
-
- let isAfter: boolean;
-
- if (columnId === leftSiblingColumnId) {
- isAfter = false;
- } else if (columnId === rightSiblingColumnId) {
- isAfter = true;
- } else {
- // check position in element
- const rect = e.currentTarget.getBoundingClientRect();
- isAfter = rect.width / 2 + (dropTarget?.[1] === "after" ? -10 : 10) < e.clientX - rect.left;
- }
-
- const newPosition = isAfter ? "after" : "before";
-
- if (columnId !== dropTarget?.[0] || newPosition !== dropTarget?.[1]) {
- setDropTarget([columnId, newPosition]);
- }
- },
- [dragging, dropTarget, setDropTarget]
- );
-
- const handleDragEnter = useCallback((e: DragEvent): void => {
- e.preventDefault();
- }, []);
-
- const handleDragEnd = useCallback((): void => {
- setDragging(undefined);
- setDropTarget(undefined);
- }, [setDropTarget, setDragging]);
-
- const handleOnDrop = useCallback(
- (_e: DragEvent): void => {
- handleDragEnd();
- if (!dragging || !dropTarget) {
- return;
- }
-
- setColumnOrder(dragging[1], dropTarget);
- },
- [handleDragEnd, setColumnOrder, dragging, dropTarget]
- );
-
- return columnsDraggable
- ? {
- draggable: true,
- onDragStart: handleDragStart,
- onDragOver: handleDragOver,
- onDrop: handleOnDrop,
- onDragEnter: handleDragEnter,
- onDragEnd: handleDragEnd
- }
- : {};
-}
-
-function getSortIcon(column: GridColumn): ReactNode {
- switch (column.sortDir) {
- case "asc":
- return ;
- case "desc":
- return ;
- default:
- return ;
- }
-}
-
-function getAriaSort(canSort: boolean, column: GridColumn): "ascending" | "descending" | "none" | undefined {
- if (!canSort) {
- return undefined;
- }
-
- switch (column.sortDir) {
- case "asc":
- return "ascending";
- case "desc":
- return "descending";
- default:
- return "none";
- }
-}
-
-function getSortProps(column: GridColumn): HTMLAttributes {
- return {
- onClick: () => {
- column.toggleSort();
- },
- onKeyDown: (e: KeyboardEvent) => {
- if (e.key === "Enter" || e.key === " ") {
- e.preventDefault();
- column.toggleSort();
- }
- },
- role: "button",
- tabIndex: 0
- };
-}
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/__tests__/Header.spec.tsx b/packages/pluggableWidgets/datagrid-web/src/components/__tests__/ColumnContainer.spec.tsx
similarity index 88%
rename from packages/pluggableWidgets/datagrid-web/src/components/__tests__/Header.spec.tsx
rename to packages/pluggableWidgets/datagrid-web/src/components/__tests__/ColumnContainer.spec.tsx
index defbdfd369..63167f0fe9 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/__tests__/Header.spec.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/__tests__/ColumnContainer.spec.tsx
@@ -7,10 +7,10 @@ import { createDatagridContainer } from "../../model/containers/createDatagridCo
import { CORE_TOKENS } from "../../model/tokens";
import { column, mockContainerProps } from "../../utils/test-utils";
import { ColumnProvider } from "../ColumnProvider";
+import { ColumnContainer } from "../ColumnContainer";
import { ColumnResizer } from "../ColumnResizer";
-import { Header, HeaderProps } from "../Header";
-describe("Header", () => {
+describe("ColumnContainer", () => {
it("renders the structure correctly", () => {
const props = mockContainerProps({
columns: [column("Column 1")]
@@ -22,7 +22,7 @@ describe("Header", () => {
const component = render(
-
+ } />
);
@@ -44,7 +44,7 @@ describe("Header", () => {
const component = render(
-
+ } />
);
@@ -66,7 +66,7 @@ describe("Header", () => {
const component = render(
- } />
+ resizer} />
);
@@ -88,7 +88,7 @@ describe("Header", () => {
const component = render(
-
+ } />
);
@@ -110,7 +110,7 @@ describe("Header", () => {
const component = render(
-
+ } />
);
@@ -134,7 +134,7 @@ describe("Header", () => {
const component = render(
-
+ } />
);
@@ -159,19 +159,10 @@ describe("Header", () => {
const component = render(
-
+ } />
);
expect(component.asFragment()).toMatchSnapshot();
});
});
-
-function mockHeaderProps(): HeaderProps {
- return {
- dropTarget: undefined,
- resizer: ,
- setDropTarget: jest.fn(),
- setIsDragging: jest.fn()
- };
-}
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/__tests__/ColumnResizer.spec.tsx b/packages/pluggableWidgets/datagrid-web/src/components/__tests__/ColumnResizer.spec.tsx
index daa0d9572b..a3aaed7e23 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/__tests__/ColumnResizer.spec.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/__tests__/ColumnResizer.spec.tsx
@@ -1,10 +1,26 @@
import "@testing-library/jest-dom";
import { render } from "@testing-library/react";
+import { ContainerProvider } from "brandi-react";
+import { createDatagridContainer } from "../../model/containers/createDatagridContainer";
+import { CORE_TOKENS as CORE } from "../../model/tokens";
+import { mockContainerProps } from "../../utils/test-utils";
+import { ColumnProvider } from "../ColumnProvider";
import { ColumnResizer } from "../ColumnResizer";
describe("Column Resizer", () => {
it("renders the structure correctly", () => {
- const component = render( );
+ const props = mockContainerProps();
+ const [container] = createDatagridContainer(props);
+ const columnsStore = container.get(CORE.columnsStore);
+ const column = columnsStore.visibleColumns[0];
+
+ const component = render(
+
+
+
+
+
+ );
expect(component).toMatchSnapshot();
});
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/__tests__/__snapshots__/Header.spec.tsx.snap b/packages/pluggableWidgets/datagrid-web/src/components/__tests__/__snapshots__/ColumnContainer.spec.tsx.snap
similarity index 79%
rename from packages/pluggableWidgets/datagrid-web/src/components/__tests__/__snapshots__/Header.spec.tsx.snap
rename to packages/pluggableWidgets/datagrid-web/src/components/__tests__/__snapshots__/ColumnContainer.spec.tsx.snap
index 6deadade40..1516a489f8 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/__tests__/__snapshots__/Header.spec.tsx.snap
+++ b/packages/pluggableWidgets/datagrid-web/src/components/__tests__/__snapshots__/ColumnContainer.spec.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Header renders the structure correctly 1`] = `
+exports[`ColumnContainer renders the structure correctly 1`] = `
-
+
Column 1
@@ -29,7 +31,7 @@ exports[`Header renders the structure correctly 1`] = `
`;
-exports[`Header renders the structure correctly when draggable 1`] = `
+exports[`ColumnContainer renders the structure correctly when draggable 1`] = `
-
+
+ ⠿
+
+
Column 1
@@ -59,7 +68,7 @@ exports[`Header renders the structure correctly when draggable 1`] = `
`;
-exports[`Header renders the structure correctly when filterable with custom filter 1`] = `
+exports[`ColumnContainer renders the structure correctly when filterable with custom filter 1`] = `
-
+
Column 1
@@ -92,7 +103,7 @@ exports[`Header renders the structure correctly when filterable with custom filt
`;
-exports[`Header renders the structure correctly when resizable 1`] = `
+exports[`ColumnContainer renders the structure correctly when resizable 1`] = `
-
+
Column 1
@@ -121,7 +134,7 @@ exports[`Header renders the structure correctly when resizable 1`] = `
`;
-exports[`Header renders the structure correctly when sortable 1`] = `
+exports[`ColumnContainer renders the structure correctly when sortable 1`] = `
-
+
Column 1
`;
-exports[`Header renders the structure correctly when value is empty 1`] = `
+exports[`ColumnContainer renders the structure correctly when value is empty 1`] = `
-
+
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/icons/FaGripVertical.tsx b/packages/pluggableWidgets/datagrid-web/src/components/icons/FaGripVertical.tsx
new file mode 100644
index 0000000000..b0e198d488
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/components/icons/FaGripVertical.tsx
@@ -0,0 +1,15 @@
+import { ReactElement } from "react";
+
+/**
+ * Custom drag handle icon with 6 aligned dots in 3 rows
+ */
+export function FaGripVertical(): ReactElement {
+ return (
+
+
+
+ );
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/features/column/HeaderDragnDrop.store.ts b/packages/pluggableWidgets/datagrid-web/src/features/column/HeaderDragnDrop.store.ts
new file mode 100644
index 0000000000..54fc13b982
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/features/column/HeaderDragnDrop.store.ts
@@ -0,0 +1,41 @@
+import { action, makeAutoObservable } from "mobx";
+import { ColumnId } from "../../typings/GridColumn";
+
+/**
+ * MobX store for managing drag & drop state of column headers.
+ * Tracks which column is being dragged and where it can be dropped.
+ * @injectable
+ */
+export class HeaderDragnDropStore {
+ private _dragOver: [ColumnId, "before" | "after"] | undefined = undefined;
+ private _isDragging: [ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined = undefined;
+
+ constructor() {
+ makeAutoObservable(this, {
+ setDragOver: action,
+ setIsDragging: action,
+ clearDragState: action
+ });
+ }
+
+ get dragOver(): [ColumnId, "before" | "after"] | undefined {
+ return this._dragOver;
+ }
+
+ get isDragging(): [ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined {
+ return this._isDragging;
+ }
+
+ setDragOver(value: [ColumnId, "before" | "after"] | undefined): void {
+ this._dragOver = value;
+ }
+
+ setIsDragging(value: [ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined): void {
+ this._isDragging = value;
+ }
+
+ clearDragState(): void {
+ this._dragOver = undefined;
+ this._isDragging = undefined;
+ }
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/features/column/HeaderDragnDrop.viewModel.ts b/packages/pluggableWidgets/datagrid-web/src/features/column/HeaderDragnDrop.viewModel.ts
new file mode 100644
index 0000000000..fed47eca29
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/features/column/HeaderDragnDrop.viewModel.ts
@@ -0,0 +1,95 @@
+import { makeAutoObservable } from "mobx";
+import { DragEvent } from "react";
+import { HeaderDragnDropStore } from "./HeaderDragnDrop.store";
+import { ColumnId, GridColumn } from "../../typings/GridColumn";
+import { ColumnGroupStore } from "../../helpers/state/ColumnGroupStore";
+
+/**
+ * View model for a single column header drag & drop interactions.
+ * Encapsulates previous `useDraggable` hook logic and uses MobX store for shared drag state.
+ * @injectable
+ */
+export class HeaderDragnDropViewModel {
+ constructor(
+ private dndStore: HeaderDragnDropStore,
+ private columnsStore: ColumnGroupStore,
+ private config: { columnsDraggable: boolean },
+ private column: GridColumn
+ ) {
+ makeAutoObservable(this);
+ }
+
+ get dropTarget(): [ColumnId, "before" | "after"] | undefined {
+ return this.dndStore.dragOver;
+ }
+
+ get dragging(): [ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined {
+ return this.dndStore.isDragging;
+ }
+
+ get isDraggable(): boolean {
+ return this.config.columnsDraggable && this.column.canDrag;
+ }
+
+ handleDragStart = (e: DragEvent): void => {
+ const elt = (e.target as HTMLDivElement).closest(".th") as HTMLDivElement;
+ if (!elt) {
+ return;
+ }
+ const columnId = (elt.dataset.columnId ?? "") as ColumnId;
+ const columnAtTheLeft = (elt.previousElementSibling as HTMLDivElement)?.dataset?.columnId as ColumnId;
+ const columnAtTheRight = (elt.nextElementSibling as HTMLDivElement)?.dataset?.columnId as ColumnId;
+ this.dndStore.setIsDragging([columnAtTheLeft, columnId, columnAtTheRight]);
+ };
+
+ handleDragOver = (e: DragEvent): void => {
+ const dragging = this.dragging;
+ if (!dragging) {
+ return;
+ }
+ const columnId = (e.currentTarget as HTMLDivElement).dataset.columnId as ColumnId;
+ if (!columnId) {
+ return;
+ }
+ e.preventDefault();
+ const [leftSiblingColumnId, draggingColumnId, rightSiblingColumnId] = dragging;
+ if (columnId === draggingColumnId) {
+ if (this.dropTarget !== undefined) {
+ this.dndStore.setDragOver(undefined);
+ }
+ return;
+ }
+ let isAfter: boolean;
+ if (columnId === leftSiblingColumnId) {
+ isAfter = false;
+ } else if (columnId === rightSiblingColumnId) {
+ isAfter = true;
+ } else {
+ const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();
+ isAfter = rect.width / 2 + (this.dropTarget?.[1] === "after" ? -10 : 10) < e.clientX - rect.left;
+ }
+ const newPosition: "before" | "after" = isAfter ? "after" : "before";
+ if (columnId !== this.dropTarget?.[0] || newPosition !== this.dropTarget?.[1]) {
+ this.dndStore.setDragOver([columnId, newPosition]);
+ }
+ };
+
+ handleDragEnter = (e: DragEvent): void => {
+ e.preventDefault();
+ };
+
+ handleDragEnd = (): void => {
+ this.dndStore.clearDragState();
+ };
+
+ handleOnDrop = (_e: DragEvent): void => {
+ const dragging = this.dragging;
+ const dropTarget = this.dropTarget;
+ this.handleDragEnd();
+ if (!dragging || !dropTarget) {
+ return;
+ }
+ // Reorder columns using existing columns store logic
+ this.columnsStore.swapColumns(dragging[1], dropTarget);
+ };
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/features/column/__tests__/HeaderDragnDrop.viewModel.spec.ts b/packages/pluggableWidgets/datagrid-web/src/features/column/__tests__/HeaderDragnDrop.viewModel.spec.ts
new file mode 100644
index 0000000000..a11aa24123
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/features/column/__tests__/HeaderDragnDrop.viewModel.spec.ts
@@ -0,0 +1,213 @@
+import { DragEvent } from "react";
+import { HeaderDragnDropViewModel } from "../HeaderDragnDrop.viewModel";
+import { HeaderDragnDropStore } from "../HeaderDragnDrop.store";
+import { ColumnId } from "../../../typings/GridColumn";
+
+describe("ColumnHeaderViewModel", () => {
+ let dndStore: HeaderDragnDropStore;
+ let mockColumnsStore: any;
+ let mockColumn: any;
+
+ beforeEach(() => {
+ dndStore = new HeaderDragnDropStore();
+ mockColumnsStore = {
+ swapColumns: jest.fn()
+ };
+ mockColumn = {
+ canDrag: true,
+ columnId: "col1" as ColumnId
+ };
+ });
+
+ describe("when columnsDraggable is false", () => {
+ it("is not draggable", () => {
+ const vm = new HeaderDragnDropViewModel(
+ dndStore,
+ mockColumnsStore,
+ { columnsDraggable: false },
+ mockColumn
+ );
+
+ expect(vm.isDraggable).toBe(false);
+ });
+ });
+
+ describe("when columnsDraggable is true", () => {
+ let vm: HeaderDragnDropViewModel;
+
+ beforeEach(() => {
+ vm = new HeaderDragnDropViewModel(dndStore, mockColumnsStore, { columnsDraggable: true }, mockColumn);
+ });
+
+ it("is draggable", () => {
+ expect(vm.isDraggable).toBe(true);
+ });
+
+ describe("handleDragStart", () => {
+ it("sets dragging state with column siblings", () => {
+ const mockElement = createMockElement("col1", "col0", "col2");
+ const event = createMockDragEvent(mockElement);
+
+ vm.handleDragStart(event);
+
+ expect(dndStore.isDragging).toEqual(["col0", "col1", "col2"]);
+ });
+
+ it("handles missing previous sibling", () => {
+ const mockElement = createMockElement("col1", undefined, "col2");
+ const event = createMockDragEvent(mockElement);
+
+ vm.handleDragStart(event);
+
+ expect(dndStore.isDragging).toEqual([undefined, "col1", "col2"]);
+ });
+
+ it("handles missing next sibling", () => {
+ const mockElement = createMockElement("col1", "col0", undefined);
+ const event = createMockDragEvent(mockElement);
+
+ vm.handleDragStart(event);
+
+ expect(dndStore.isDragging).toEqual(["col0", "col1", undefined]);
+ });
+
+ it("does nothing when element is not found", () => {
+ const event = {
+ target: {
+ closest: jest.fn().mockReturnValue(null)
+ }
+ } as any;
+
+ vm.handleDragStart(event);
+
+ expect(dndStore.isDragging).toBeUndefined();
+ });
+ });
+
+ describe("handleDragOver", () => {
+ beforeEach(() => {
+ dndStore.setIsDragging(["col0" as ColumnId, "col1" as ColumnId, "col2" as ColumnId]);
+ });
+
+ it("does nothing when not dragging", () => {
+ dndStore.clearDragState();
+ const event = createMockDragOverEvent("col2", 100, 50);
+
+ vm.handleDragOver(event);
+
+ expect(dndStore.dragOver).toBeUndefined();
+ });
+
+ it("does nothing when columnId is missing", () => {
+ const event = createMockDragOverEvent("", 100, 50);
+
+ vm.handleDragOver(event);
+
+ expect(dndStore.dragOver).toBeUndefined();
+ });
+
+ it("clears dropTarget when hovering over self", () => {
+ dndStore.setDragOver(["col2" as ColumnId, "after"]);
+ const event = createMockDragOverEvent("col1", 100, 50);
+
+ vm.handleDragOver(event);
+
+ expect(dndStore.dragOver).toBeUndefined();
+ });
+
+ it("sets dropTarget to before when hovering over left sibling", () => {
+ const event = createMockDragOverEvent("col0", 100, 50);
+
+ vm.handleDragOver(event);
+
+ expect(dndStore.dragOver).toEqual(["col0", "before"]);
+ });
+
+ it("sets dropTarget to after when hovering over right sibling", () => {
+ const event = createMockDragOverEvent("col2", 100, 50);
+
+ vm.handleDragOver(event);
+
+ expect(dndStore.dragOver).toEqual(["col2", "after"]);
+ });
+ });
+
+ describe("handleDragEnter", () => {
+ it("prevents default behavior", () => {
+ const event = { preventDefault: jest.fn() } as any;
+
+ vm.handleDragEnter(event);
+
+ expect(event.preventDefault).toHaveBeenCalled();
+ });
+ });
+
+ describe("handleDragEnd", () => {
+ it("clears drag state", () => {
+ dndStore.setIsDragging(["col0" as ColumnId, "col1" as ColumnId, "col2" as ColumnId]);
+ dndStore.setDragOver(["col2" as ColumnId, "after"]);
+
+ vm.handleDragEnd();
+
+ expect(dndStore.isDragging).toBeUndefined();
+ expect(dndStore.dragOver).toBeUndefined();
+ });
+ });
+
+ describe("handleOnDrop", () => {
+ it("calls swapColumns with correct parameters", () => {
+ dndStore.setIsDragging(["col0" as ColumnId, "col1" as ColumnId, "col2" as ColumnId]);
+ dndStore.setDragOver(["col3" as ColumnId, "after"]);
+
+ vm.handleOnDrop({} as any);
+
+ expect(mockColumnsStore.swapColumns).toHaveBeenCalledWith("col1", ["col3", "after"]);
+ });
+
+ it("clears drag state after drop", () => {
+ dndStore.setIsDragging(["col0" as ColumnId, "col1" as ColumnId, "col2" as ColumnId]);
+ dndStore.setDragOver(["col3" as ColumnId, "after"]);
+
+ vm.handleOnDrop({} as any);
+
+ expect(dndStore.isDragging).toBeUndefined();
+ expect(dndStore.dragOver).toBeUndefined();
+ });
+ });
+ });
+});
+
+// Helper functions to create mock DOM elements and events
+
+function createMockElement(
+ columnId: string,
+ prevSiblingId: string | undefined,
+ nextSiblingId: string | undefined
+): HTMLDivElement {
+ const element = {
+ dataset: { columnId },
+ previousElementSibling: prevSiblingId ? { dataset: { columnId: prevSiblingId } } : null,
+ nextElementSibling: nextSiblingId ? { dataset: { columnId: nextSiblingId } } : null
+ } as any;
+
+ return element;
+}
+
+function createMockDragEvent(targetElement: HTMLDivElement): DragEvent {
+ return {
+ target: {
+ closest: jest.fn().mockReturnValue(targetElement)
+ }
+ } as any;
+}
+
+function createMockDragOverEvent(columnId: string, width: number, clientX: number): DragEvent {
+ return {
+ currentTarget: {
+ dataset: { columnId },
+ getBoundingClientRect: jest.fn().mockReturnValue({ width, left: 0 })
+ },
+ clientX,
+ preventDefault: jest.fn()
+ } as any;
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/containers/Datagrid.container.ts b/packages/pluggableWidgets/datagrid-web/src/model/containers/Datagrid.container.ts
index fd99730e77..c92a252400 100644
--- a/packages/pluggableWidgets/datagrid-web/src/model/containers/Datagrid.container.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/model/containers/Datagrid.container.ts
@@ -22,6 +22,8 @@ import { createCellEventsController } from "../../features/row-interaction/CellE
import { creteCheckboxEventsController } from "../../features/row-interaction/CheckboxEventsController";
import { SelectAllModule } from "../../features/select-all/SelectAllModule.container";
import { ColumnGroupStore } from "../../helpers/state/ColumnGroupStore";
+import { HeaderDragnDropStore } from "../../features/column/HeaderDragnDrop.store";
+import { HeaderDragnDropViewModel } from "../../features/column/HeaderDragnDrop.viewModel";
import { GridBasicData } from "../../helpers/state/GridBasicData";
import { GridPersonalizationStore } from "../../helpers/state/GridPersonalizationStore";
import { DatagridConfig } from "../configs/Datagrid.config";
@@ -84,6 +86,10 @@ injected(
DG.selectionCounterCfg.optional
);
+// drag and drop
+injected(HeaderDragnDropStore);
+injected(HeaderDragnDropViewModel, DG.headerDragDrop, CORE.columnsStore, CORE.config, CORE.column);
+
export class DatagridContainer extends Container {
id = `DatagridContainer@${generateUUID()}`;
constructor(root: Container) {
@@ -94,6 +100,10 @@ export class DatagridContainer extends Container {
this.bind(DG.basicDate).toInstance(GridBasicData).inSingletonScope();
// Columns store
this.bind(CORE.columnsStore).toInstance(ColumnGroupStore).inSingletonScope();
+ // Drag and Drop store
+ this.bind(DG.headerDragDrop).toInstance(HeaderDragnDropStore).inSingletonScope();
+ // Drag and Drop view model
+ this.bind(DG.headerDragnDropVM).toInstance(HeaderDragnDropViewModel).inSingletonScope();
// Query service
this.bind(DG.query).toInstance(DatasourceService).inSingletonScope();
// Pagination service
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/hooks/injection-hooks.ts b/packages/pluggableWidgets/datagrid-web/src/model/hooks/injection-hooks.ts
index 337a03637f..c09a2d2dcf 100644
--- a/packages/pluggableWidgets/datagrid-web/src/model/hooks/injection-hooks.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/model/hooks/injection-hooks.ts
@@ -20,6 +20,7 @@ export const [useRowClass] = createInjectionHooks(DG.rowClass);
export const [useDatagridRootVM] = createInjectionHooks(DG.datagridRootVM);
export const [useRows] = createInjectionHooks(CORE.rows);
export const [useSelectActions] = createInjectionHooks(DG.selectActions);
+export const [useHeaderDragnDropVM] = createInjectionHooks(DG.headerDragnDropVM);
export const [useClickActionHelper] = createInjectionHooks(DG.clickActionHelper);
export const [useFocusService] = createInjectionHooks(DG.focusService);
export const [useCheckboxEventsHandler] = createInjectionHooks(DG.checkboxEventsHandler);
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/tokens.ts b/packages/pluggableWidgets/datagrid-web/src/model/tokens.ts
index 26b3e38201..1bb6a87af4 100644
--- a/packages/pluggableWidgets/datagrid-web/src/model/tokens.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/model/tokens.ts
@@ -33,6 +33,8 @@ import { SelectionProgressDialogViewModel } from "../features/select-all/Selecti
import { ColumnGroupStore } from "../helpers/state/ColumnGroupStore";
import { GridBasicData } from "../helpers/state/GridBasicData";
import { GridPersonalizationStore } from "../helpers/state/GridPersonalizationStore";
+import { HeaderDragnDropStore } from "../features/column/HeaderDragnDrop.store";
+import { HeaderDragnDropViewModel } from "../features/column/HeaderDragnDrop.viewModel";
import { DatasourceParamsController } from "../model/services/DatasourceParamsController";
import { GridColumn } from "../typings/GridColumn";
import { DatagridConfig } from "./configs/Datagrid.config";
@@ -131,6 +133,8 @@ export const DG_TOKENS = {
clickActionHelper: token("@service:ClickActionHelper"),
focusService: token("@service:FocusTargetController"),
checkboxEventsHandler: token("@service:CheckboxEventsController"),
+ headerDragDrop: token("@store:HeaderDragnDropStore"),
+ headerDragnDropVM: token("@viewmodel:ColumnHeaderViewModel"),
cellEventsHandler: token("@service:CellEventsController")
};
diff --git a/packages/pluggableWidgets/gallery-web/src/helpers/useItemSelectHelper.ts b/packages/pluggableWidgets/gallery-web/src/helpers/useItemSelectHelper.ts
index 050128f4e0..2e291dbca8 100644
--- a/packages/pluggableWidgets/gallery-web/src/helpers/useItemSelectHelper.ts
+++ b/packages/pluggableWidgets/gallery-web/src/helpers/useItemSelectHelper.ts
@@ -6,5 +6,5 @@ export function useItemSelectHelper(
selectionHelper: SelectionHelper | undefined
): SelectActionHandler {
// eslint-disable-next-line react-hooks/exhaustive-deps
- return useMemo(() => new SelectActionHandler(selection, selectionHelper), [selectionHelper]);
+ return useMemo(() => new SelectActionHandler(selection, selectionHelper ?? null), [selectionHelper]);
}
diff --git a/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx b/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx
index 0d5ac84aa3..2caffacba8 100644
--- a/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx
+++ b/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx
@@ -81,7 +81,7 @@ export function createMockGalleryContext(): GalleryRootScope {
refreshInterval: 0
});
- const mockSelectHelper = new SelectActionHandler("None", undefined);
+ const mockSelectHelper = new SelectActionHandler("None", null);
return {
rootStore: mockStore,
@@ -110,7 +110,7 @@ type Mocks = {
export function mockProps(params: Helpers & Mocks = {}): GalleryProps {
const {
onClick = undefined,
- selectHelper = new SelectActionHandler("None", undefined),
+ selectHelper = new SelectActionHandler("None", null),
actionHelper = new ClickActionHelper("single", onClick),
focusController = new FocusTargetController(new PositionController(), new VirtualGridLayout(3, 4, 10)),
itemEventsController = new ItemEventsController(
diff --git a/packages/shared/widget-plugin-grid/src/selection/select-action-handler.ts b/packages/shared/widget-plugin-grid/src/selection/select-action-handler.ts
index 165f58d6cb..c5bcccad46 100644
--- a/packages/shared/widget-plugin-grid/src/selection/select-action-handler.ts
+++ b/packages/shared/widget-plugin-grid/src/selection/select-action-handler.ts
@@ -6,7 +6,7 @@ import { SelectAdjacentFx, SelectAllFx, SelectFx, SelectionType, WidgetSelection
export class SelectActionHandler {
constructor(
private selection: WidgetSelectionProperty,
- protected selectionHelper: SelectionHelperService | undefined
+ protected selectionHelper: SelectionHelperService
) {}
get selectionType(): SelectionType {