Skip to content

Commit c8596c2

Browse files
mtsvyatkovagedinakovarkaraivanov
authored
refactor(tile): Improved tile swap behavior based on moving direction (#1608)
* Added better support for drag operations in RTL context --------- Co-authored-by: Galina Edinakova <[email protected]> Co-authored-by: Radoslav Karaivanov <[email protected]>
1 parent 3597e68 commit c8596c2

File tree

4 files changed

+106
-13
lines changed

4 files changed

+106
-13
lines changed

src/components/common/controllers/drag.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import type {
66
import type { Ref } from 'lit/directives/ref.js';
77

88
import { getDefaultLayer } from '../../resize-container/default-ghost.js';
9-
import { findElementFromEventPath, getRoot, roundByDPR } from '../util.js';
9+
import {
10+
findElementFromEventPath,
11+
getRoot,
12+
isLTR,
13+
roundByDPR,
14+
} from '../util.js';
1015

1116
type DragCallback = (parameters: DragCallbackParameters) => unknown;
1217
type DragCancelCallback = (state: DragState) => unknown;
@@ -21,13 +26,20 @@ type State = {
2126
current: DOMRect;
2227
position: { x: number; y: number };
2328
offset: { x: number; y: number };
29+
pointerState: {
30+
initial: { initialX: number; initialY: number };
31+
current: { currentX: number; currentY: number };
32+
direction: Direction;
33+
};
2434
};
2535

2636
type DragState = State & {
2737
ghost: HTMLElement | null;
2838
element: Element | null;
2939
};
3040

41+
type Direction = 'start' | 'end' | 'bottom' | 'top';
42+
3143
type DragControllerConfiguration = {
3244
/** Whether the drag feature is enabled for the current host. */
3345
enabled?: boolean;
@@ -157,13 +169,30 @@ class DragController implements ReactiveController {
157169
}
158170

159171
private get _stateParameters(): DragState {
172+
this._state.pointerState.direction = this._trackPointerMovement();
173+
160174
return {
161175
...this._state,
162176
ghost: this._ghost,
163177
element: this._matchedElement,
164178
};
165179
}
166180

181+
private _trackPointerMovement(): Direction {
182+
const { initialX, initialY } = this._state.pointerState.initial;
183+
const { currentX, currentY } = this._state.pointerState.current;
184+
const deltaX = currentX - initialX;
185+
const deltaY = currentY - initialY;
186+
const LTR = isLTR(this._host);
187+
188+
const isHorizontalMove = Math.abs(deltaX) >= Math.abs(deltaY);
189+
190+
if (isHorizontalMove) {
191+
return (LTR ? deltaX >= 0 : deltaX <= 0) ? 'end' : 'start';
192+
}
193+
return deltaY >= 0 ? 'bottom' : 'top';
194+
}
195+
167196
constructor(
168197
host: ReactiveControllerHost & LitElement,
169198
options?: DragControllerConfiguration
@@ -249,7 +278,11 @@ class DragController implements ReactiveController {
249278
this._createDragGhost();
250279
this._updatePosition(event);
251280

252-
const parameters = { event, state: this._stateParameters };
281+
const parameters = {
282+
event,
283+
state: this._stateParameters,
284+
};
285+
253286
if (this._options.start?.call(this._host, parameters) === false) {
254287
this.dispose();
255288
return;
@@ -267,7 +300,20 @@ class DragController implements ReactiveController {
267300
this._updatePosition(event);
268301
this._updateMatcher(event);
269302

270-
const parameters = { event, state: this._stateParameters };
303+
this._state.pointerState.initial = {
304+
initialX: this._state.pointerState.current.currentX,
305+
initialY: this._state.pointerState.current.currentY,
306+
};
307+
this._state.pointerState.current = {
308+
currentX: event.clientX,
309+
currentY: event.clientY,
310+
};
311+
312+
const parameters = {
313+
event,
314+
state: this._stateParameters,
315+
};
316+
271317
this._options.move?.call(this._host, parameters);
272318

273319
this._assignPosition(this._dragItem);
@@ -313,6 +359,11 @@ class DragController implements ReactiveController {
313359
current: structuredClone(rect),
314360
position,
315361
offset,
362+
pointerState: {
363+
initial: { initialX: clientX, initialY: clientY },
364+
current: { currentX: clientX, currentY: clientY },
365+
direction: 'end',
366+
},
316367
};
317368
}
318369

src/components/tile-manager/tile-dnd.spec.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe('Tile drag and drop', () => {
7575
tile,
7676
{ clientX: opts.x, clientY: opts.y },
7777
{ x: opts.dx, y: opts.dy },
78-
2
78+
3
7979
);
8080
}
8181

@@ -292,6 +292,22 @@ describe('Tile drag and drop', () => {
292292
await elementUpdated(draggedTile);
293293
});
294294

295+
it('should swap positions properly in RTL mode', async () => {
296+
tileManager.dir = 'rtl';
297+
const draggedTile = getTile(0);
298+
const dropTarget = getTile(1);
299+
300+
await elementUpdated(tileManager);
301+
302+
expect(draggedTile.position).to.equal(0);
303+
expect(dropTarget.position).to.equal(1);
304+
305+
await dragAndDrop(draggedTile, dropTarget);
306+
307+
expect(draggedTile.position).to.equal(1);
308+
expect(dropTarget.position).to.equal(0);
309+
});
310+
295311
it('should swap positions properly when row, column and span are specified', async () => {
296312
const draggedTile = getTile(0);
297313
const dropTarget = getTile(1);

src/components/tile-manager/tile-ghost-util.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isLTR } from '../common/util.js';
12
import type IgcTileComponent from './tile.js';
23

34
export function createTileDragGhost(tile: IgcTileComponent): IgcTileComponent {
@@ -13,6 +14,7 @@ export function createTileDragGhost(tile: IgcTileComponent): IgcTileComponent {
1314
});
1415

1516
Object.assign(clone.style, {
17+
direction: isLTR(tile) ? 'ltr' : 'rtl',
1618
position: 'absolute',
1719
contain: 'strict',
1820
top: 0,

src/components/tile-manager/tile.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ import {
2626
asNumber,
2727
createCounter,
2828
findElementFromEventPath,
29-
getCenterPoint,
3029
isEmpty,
30+
isLTR,
3131
partNameMap,
3232
} from '../common/util.js';
3333
import IgcDividerComponent from '../divider/divider.js';
@@ -135,6 +135,7 @@ export default class IgcTileComponent extends EventEmitterMixin<
135135
private _position = -1;
136136
private _resizeState = createTileResizeState();
137137
private _dragStack = createTileDragStack();
138+
private _previousPointerPosition: { x: number; y: number } | null = null;
138139

139140
private _customAdorners = new Map<string, boolean>(
140141
Object.entries({
@@ -399,17 +400,40 @@ export default class IgcTileComponent extends EventEmitterMixin<
399400
return true;
400401
}
401402

402-
private _handleDragOver(parameters: DragCallbackParameters) {
403+
private _handleDragOver(parameters: DragCallbackParameters): void {
403404
const match = parameters.state.element as IgcTileComponent;
404-
const { clientX, clientY } = parameters.event;
405405

406406
if (this._dragStack.peek() === match) {
407-
const { x, y } = getCenterPoint(match);
408-
409-
const shouldSwap =
410-
this.position <= match.position
411-
? clientX > x || clientY > y
412-
: clientX < x || clientY < y;
407+
const direction = parameters.state.pointerState.direction;
408+
const { clientX, clientY } = parameters.event;
409+
const { left, top, width, height } = match.getBoundingClientRect();
410+
const relativeX = (clientX - left) / width;
411+
const relativeY = (clientY - top) / height;
412+
const LTR = isLTR(this);
413+
414+
let shouldSwap = false;
415+
416+
switch (direction) {
417+
case 'start':
418+
shouldSwap =
419+
this.position > match.position &&
420+
(LTR ? relativeX <= 0.25 : relativeX >= 0.75);
421+
break;
422+
423+
case 'end':
424+
shouldSwap =
425+
this.position < match.position &&
426+
(LTR ? relativeX >= 0.75 : relativeX <= 0.25);
427+
break;
428+
429+
case 'top':
430+
shouldSwap = this.position > match.position && relativeY <= 0.25;
431+
break;
432+
433+
case 'bottom':
434+
shouldSwap = this.position < match.position && relativeY >= 0.75;
435+
break;
436+
}
413437

414438
if (shouldSwap) {
415439
this._dragStack.pop();

0 commit comments

Comments
 (0)