Skip to content

Commit 053558d

Browse files
committed
refactor: extract drag & drop state and logic to mobx
1 parent 1631ed6 commit 053558d

File tree

5 files changed

+156
-0
lines changed

5 files changed

+156
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { makeAutoObservable } from "mobx";
2+
import { DragEvent, DragEventHandler } from "react";
3+
import { HeaderDragnDropStore } from "./HeaderDragnDrop.store";
4+
import { ColumnId } from "../../typings/GridColumn";
5+
import { ColumnGroupStore } from "../../helpers/state/ColumnGroupStore";
6+
7+
/**
8+
* View model for a single column header drag & drop interactions.
9+
* Encapsulates previous `useDraggable` hook logic and uses MobX store for shared drag state.
10+
*/
11+
export class ColumnHeaderViewModel {
12+
private readonly dndStore: HeaderDragnDropStore;
13+
private readonly columnsStore: ColumnGroupStore;
14+
private readonly columnsDraggable: boolean;
15+
16+
constructor(params: { dndStore: HeaderDragnDropStore; columnsStore: ColumnGroupStore; columnsDraggable: boolean }) {
17+
this.dndStore = params.dndStore;
18+
this.columnsStore = params.columnsStore;
19+
this.columnsDraggable = params.columnsDraggable;
20+
makeAutoObservable(this);
21+
}
22+
23+
get dropTarget(): [ColumnId, "before" | "after"] | undefined {
24+
return this.dndStore.dragOver;
25+
}
26+
27+
get dragging(): [ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined {
28+
return this.dndStore.isDragging;
29+
}
30+
31+
/** Handlers exposed to the component. */
32+
get draggableProps(): {
33+
draggable?: boolean;
34+
onDragStart?: DragEventHandler;
35+
onDragOver?: DragEventHandler;
36+
onDrop?: DragEventHandler;
37+
onDragEnter?: DragEventHandler;
38+
onDragEnd?: DragEventHandler;
39+
} {
40+
if (!this.columnsDraggable) {
41+
return {};
42+
}
43+
return {
44+
draggable: true,
45+
onDragStart: this.handleDragStart,
46+
onDragOver: this.handleDragOver,
47+
onDrop: this.handleOnDrop,
48+
onDragEnter: this.handleDragEnter,
49+
onDragEnd: this.handleDragEnd
50+
};
51+
}
52+
53+
private handleDragStart = (e: DragEvent<HTMLDivElement>): void => {
54+
const elt = (e.target as HTMLDivElement).closest(".th") as HTMLDivElement;
55+
if (!elt) {
56+
return;
57+
}
58+
const columnId = (elt.dataset.columnId ?? "") as ColumnId;
59+
const columnAtTheLeft = (elt.previousElementSibling as HTMLDivElement)?.dataset?.columnId as ColumnId;
60+
const columnAtTheRight = (elt.nextElementSibling as HTMLDivElement)?.dataset?.columnId as ColumnId;
61+
this.dndStore.setIsDragging([columnAtTheLeft, columnId, columnAtTheRight]);
62+
};
63+
64+
private handleDragOver = (e: DragEvent<HTMLDivElement>): void => {
65+
const dragging = this.dragging;
66+
if (!dragging) {
67+
return;
68+
}
69+
const columnId = (e.currentTarget as HTMLDivElement).dataset.columnId as ColumnId;
70+
if (!columnId) {
71+
return;
72+
}
73+
e.preventDefault();
74+
const [leftSiblingColumnId, draggingColumnId, rightSiblingColumnId] = dragging;
75+
if (columnId === draggingColumnId) {
76+
if (this.dropTarget !== undefined) {
77+
this.dndStore.setDragOver(undefined);
78+
}
79+
return;
80+
}
81+
let isAfter: boolean;
82+
if (columnId === leftSiblingColumnId) {
83+
isAfter = false;
84+
} else if (columnId === rightSiblingColumnId) {
85+
isAfter = true;
86+
} else {
87+
const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();
88+
isAfter = rect.width / 2 + (this.dropTarget?.[1] === "after" ? -10 : 10) < e.clientX - rect.left;
89+
}
90+
const newPosition: "before" | "after" = isAfter ? "after" : "before";
91+
if (columnId !== this.dropTarget?.[0] || newPosition !== this.dropTarget?.[1]) {
92+
this.dndStore.setDragOver([columnId, newPosition]);
93+
}
94+
};
95+
96+
private handleDragEnter = (e: DragEvent<HTMLDivElement>): void => {
97+
e.preventDefault();
98+
};
99+
100+
private handleDragEnd = (): void => {
101+
this.dndStore.clearDragState();
102+
};
103+
104+
private handleOnDrop = (_e: DragEvent<HTMLDivElement>): void => {
105+
const dragging = this.dragging;
106+
const dropTarget = this.dropTarget;
107+
this.handleDragEnd();
108+
if (!dragging || !dropTarget) {
109+
return;
110+
}
111+
// Reorder columns using existing columns store logic
112+
this.columnsStore.swapColumns(dragging[1], dropTarget);
113+
};
114+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { action, makeAutoObservable } from "mobx";
2+
import { ColumnId } from "../../typings/GridColumn";
3+
4+
export class HeaderDragnDropStore {
5+
private _dragOver: [ColumnId, "before" | "after"] | undefined = undefined;
6+
private _isDragging: [ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined = undefined;
7+
8+
constructor() {
9+
makeAutoObservable(this, {
10+
setDragOver: action,
11+
setIsDragging: action,
12+
clearDragState: action
13+
});
14+
}
15+
16+
get dragOver(): [ColumnId, "before" | "after"] | undefined {
17+
return this._dragOver;
18+
}
19+
20+
get isDragging(): [ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined {
21+
return this._isDragging;
22+
}
23+
24+
setDragOver(value: [ColumnId, "before" | "after"] | undefined): void {
25+
this._dragOver = value;
26+
}
27+
28+
setIsDragging(value: [ColumnId | undefined, ColumnId, ColumnId | undefined] | undefined): void {
29+
this._isDragging = value;
30+
}
31+
32+
clearDragState(): void {
33+
this._dragOver = undefined;
34+
this._isDragging = undefined;
35+
}
36+
}

packages/pluggableWidgets/datagrid-web/src/model/containers/Datagrid.container.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { createCellEventsController } from "../../features/row-interaction/CellE
2222
import { creteCheckboxEventsController } from "../../features/row-interaction/CheckboxEventsController";
2323
import { SelectAllModule } from "../../features/select-all/SelectAllModule.container";
2424
import { ColumnGroupStore } from "../../helpers/state/ColumnGroupStore";
25+
import { HeaderDragnDropStore } from "../../features/column/HeaderDragnDrop.store";
2526
import { GridBasicData } from "../../helpers/state/GridBasicData";
2627
import { GridPersonalizationStore } from "../../helpers/state/GridPersonalizationStore";
2728
import { DatagridConfig } from "../configs/Datagrid.config";
@@ -94,6 +95,8 @@ export class DatagridContainer extends Container {
9495
this.bind(DG.basicDate).toInstance(GridBasicData).inSingletonScope();
9596
// Columns store
9697
this.bind(CORE.columnsStore).toInstance(ColumnGroupStore).inSingletonScope();
98+
// Drag and Drop store
99+
this.bind(DG.headerDragDrop).toInstance(HeaderDragnDropStore).inSingletonScope();
97100
// Query service
98101
this.bind(DG.query).toInstance(DatasourceService).inSingletonScope();
99102
// Pagination service

packages/pluggableWidgets/datagrid-web/src/model/hooks/injection-hooks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const [useRowClass] = createInjectionHooks(DG.rowClass);
2020
export const [useDatagridRootVM] = createInjectionHooks(DG.datagridRootVM);
2121
export const [useRows] = createInjectionHooks(CORE.rows);
2222
export const [useSelectActions] = createInjectionHooks(DG.selectActions);
23+
export const [useHeaderDragDrop] = createInjectionHooks(DG.headerDragDrop);
2324
export const [useClickActionHelper] = createInjectionHooks(DG.clickActionHelper);
2425
export const [useFocusService] = createInjectionHooks(DG.focusService);
2526
export const [useCheckboxEventsHandler] = createInjectionHooks(DG.checkboxEventsHandler);

packages/pluggableWidgets/datagrid-web/src/model/tokens.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { SelectionProgressDialogViewModel } from "../features/select-all/Selecti
3333
import { ColumnGroupStore } from "../helpers/state/ColumnGroupStore";
3434
import { GridBasicData } from "../helpers/state/GridBasicData";
3535
import { GridPersonalizationStore } from "../helpers/state/GridPersonalizationStore";
36+
import { HeaderDragnDropStore } from "../features/column/HeaderDragnDrop.store";
3637
import { DatasourceParamsController } from "../model/services/DatasourceParamsController";
3738
import { GridColumn } from "../typings/GridColumn";
3839
import { DatagridConfig } from "./configs/Datagrid.config";
@@ -131,6 +132,7 @@ export const DG_TOKENS = {
131132
clickActionHelper: token<ClickActionHelper>("@service:ClickActionHelper"),
132133
focusService: token<FocusTargetController>("@service:FocusTargetController"),
133134
checkboxEventsHandler: token<CheckboxEventsController>("@service:CheckboxEventsController"),
135+
headerDragDrop: token<HeaderDragnDropStore>("HeaderDragnDropStore"),
134136
cellEventsHandler: token<CellEventsController>("@service:CellEventsController")
135137
};
136138

0 commit comments

Comments
 (0)