Skip to content

Commit 0e49fb8

Browse files
committed
fix(*): move drag direction to controller and add rtl checks
1 parent 4544a1f commit 0e49fb8

File tree

4 files changed

+116
-38
lines changed

4 files changed

+116
-38
lines changed

src/components/common/controllers/drag.ts

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ 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;
1318

1419
export type DragCallbackParameters = {
1520
event: PointerEvent;
1621
state: DragState;
22+
direction: Direction | null;
1723
};
1824

1925
type State = {
@@ -28,6 +34,13 @@ type DragState = State & {
2834
element: Element | null;
2935
};
3036

37+
type Direction = {
38+
isHorizontalMove: boolean;
39+
movingDown: boolean;
40+
movingToEndHorizontally: boolean;
41+
movingToStartHorizontally: boolean;
42+
};
43+
3144
type DragControllerConfiguration = {
3245
/** Whether the drag feature is enabled for the current host. */
3346
enabled?: boolean;
@@ -109,6 +122,8 @@ class DragController implements ReactiveController {
109122
private _hasPointerCapture = false;
110123

111124
private _ghost: HTMLElement | null = null;
125+
private _previousPointerPosition: { x: number; y: number } | null = null;
126+
private _direction: Direction | null = null;
112127

113128
/** Whether `snapToCursor` is enabled for the controller. */
114129
private get _hasSnapping(): boolean {
@@ -164,6 +179,29 @@ class DragController implements ReactiveController {
164179
};
165180
}
166181

182+
private _trackPointerMovement(
183+
clientX: number,
184+
clientY: number
185+
): {
186+
isHorizontalMove: boolean;
187+
movingDown: boolean;
188+
movingToEndHorizontally: boolean;
189+
movingToStartHorizontally: boolean;
190+
} {
191+
const deltaX = clientX - (this._previousPointerPosition?.x ?? clientX);
192+
const deltaY = clientY - (this._previousPointerPosition?.y ?? clientY);
193+
const LTR = isLTR(this._host);
194+
195+
this._previousPointerPosition = { x: clientX, y: clientY };
196+
197+
return {
198+
isHorizontalMove: Math.abs(deltaX) >= Math.abs(deltaY),
199+
movingDown: deltaY >= 0,
200+
movingToEndHorizontally: LTR ? deltaX >= 0 : deltaX <= 0,
201+
movingToStartHorizontally: LTR ? deltaX < 0 : deltaX > 0,
202+
};
203+
}
204+
167205
constructor(
168206
host: ReactiveControllerHost & LitElement,
169207
options?: DragControllerConfiguration
@@ -249,7 +287,15 @@ class DragController implements ReactiveController {
249287
this._createDragGhost();
250288
this._updatePosition(event);
251289

252-
const parameters = { event, state: this._stateParameters };
290+
this._previousPointerPosition = { x: event.clientX, y: event.clientY };
291+
this._direction = this._trackPointerMovement(event.clientX, event.clientY);
292+
293+
const parameters = {
294+
event,
295+
state: this._stateParameters,
296+
direction: this._direction,
297+
};
298+
253299
if (this._options.start?.call(this._host, parameters) === false) {
254300
this.dispose();
255301
return;
@@ -267,16 +313,30 @@ class DragController implements ReactiveController {
267313
this._updatePosition(event);
268314
this._updateMatcher(event);
269315

270-
const parameters = { event, state: this._stateParameters };
316+
this._direction = this._trackPointerMovement(event.clientX, event.clientY);
317+
318+
const parameters = {
319+
event,
320+
state: this._stateParameters,
321+
direction: this._direction,
322+
};
323+
271324
this._options.move?.call(this._host, parameters);
272325

273326
this._assignPosition(this._dragItem);
274327
}
275328

276329
private _handlePointerEnd(event: PointerEvent): void {
330+
this._previousPointerPosition = null;
277331
this._options.end?.call(this._host, {
278332
event,
279333
state: this._stateParameters,
334+
direction: {
335+
isHorizontalMove: false,
336+
movingDown: false,
337+
movingToEndHorizontally: false,
338+
movingToStartHorizontally: false,
339+
},
280340
});
281341
this.dispose();
282342
}
@@ -353,6 +413,7 @@ class DragController implements ReactiveController {
353413
this._options.enter?.call(this._host, {
354414
event,
355415
state: this._stateParameters,
416+
direction: this._direction,
356417
});
357418
return;
358419
}
@@ -361,6 +422,7 @@ class DragController implements ReactiveController {
361422
this._options.leave?.call(this._host, {
362423
event,
363424
state: this._stateParameters,
425+
direction: this._direction,
364426
});
365427
this._matchedElement = null;
366428
return;
@@ -370,6 +432,7 @@ class DragController implements ReactiveController {
370432
this._options.over?.call(this._host, {
371433
event,
372434
state: this._stateParameters,
435+
direction: this._direction,
373436
});
374437
}
375438
}

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: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
createCounter,
2828
findElementFromEventPath,
2929
isEmpty,
30+
isLTR,
3031
partNameMap,
3132
} from '../common/util.js';
3233
import IgcDividerComponent from '../divider/divider.js';
@@ -399,43 +400,41 @@ 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;
405-
const { left, top, width, height } = match.getBoundingClientRect();
406405

407-
const relativeX = (clientX - left) / width;
408-
const relativeY = (clientY - top) / height;
409-
410-
if (this._previousPointerPosition) {
411-
const deltaX = clientX - this._previousPointerPosition.x;
412-
const deltaY = clientY - this._previousPointerPosition.y;
413-
414-
const movingRight = deltaX > 0;
415-
const movingLeft = deltaX < 0;
416-
const movingDown = deltaY > 0;
417-
const movingUp = deltaY < 0;
418-
419-
const isHorizontalMove = Math.abs(deltaX) > Math.abs(deltaY);
420-
const isVerticalMove = Math.abs(deltaY) > Math.abs(deltaX);
406+
if (this._dragStack.peek() === match) {
407+
if (!parameters.direction) return;
408+
409+
const {
410+
isHorizontalMove,
411+
movingDown,
412+
movingToEndHorizontally,
413+
movingToStartHorizontally,
414+
} = parameters.direction;
415+
const { clientX, clientY } = parameters.event;
416+
const { left, top, width, height } = match.getBoundingClientRect();
417+
const relativeX = (clientX - left) / width;
418+
const relativeY = (clientY - top) / height;
419+
const LTR = isLTR(this);
421420

422421
const shouldSwap =
423422
(this.position < match.position &&
424423
isHorizontalMove &&
425-
movingRight &&
426-
relativeX > 0.75) ||
424+
movingToEndHorizontally &&
425+
(LTR ? relativeX >= 0.75 : relativeX <= 0.25)) ||
427426
(this.position > match.position &&
428427
isHorizontalMove &&
429-
movingLeft &&
430-
relativeX < 0.25) ||
428+
movingToStartHorizontally &&
429+
(LTR ? relativeX <= 0.25 : relativeX >= 0.75)) ||
431430
(this.position < match.position &&
432-
isVerticalMove &&
431+
!isHorizontalMove &&
433432
movingDown &&
434-
relativeY > 0.75) ||
433+
relativeY >= 0.75) ||
435434
(this.position > match.position &&
436-
isVerticalMove &&
437-
movingUp &&
438-
relativeY < 0.25);
435+
!isHorizontalMove &&
436+
!movingDown &&
437+
relativeY <= 0.25);
439438

440439
if (shouldSwap) {
441440
this._dragStack.pop();
@@ -445,17 +444,15 @@ export default class IgcTileComponent extends EventEmitterMixin<
445444
swapTiles(this, match);
446445
});
447446
}
448-
}
449447

450-
this._previousPointerPosition = { x: clientX, y: clientY };
448+
return;
449+
}
451450

452-
if (this._dragStack.peek() !== match) {
453-
this._dragStack.push(match);
451+
this._dragStack.push(match);
454452

455-
startViewTransition(() => {
456-
swapTiles(this, match);
457-
});
458-
}
453+
startViewTransition(() => {
454+
swapTiles(this, match);
455+
});
459456
}
460457

461458
private _handleDragCancel() {

0 commit comments

Comments
 (0)