Skip to content

Commit c031a8e

Browse files
committed
fix(tile): Dragging behavior and clean-ups
Reduced tile shifts while dragging differently sized tiles. Tile's actions DOM part will no longer trigger drag behavior even in `tile` drag mode. Removed auto-scroll behavior for now. Added tests for drag behavior with tile actions slot children.
1 parent 5790c0d commit c031a8e

File tree

7 files changed

+151
-125
lines changed

7 files changed

+151
-125
lines changed

src/components/common/util.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,13 @@ export function omit<T extends object>(entry: T, ...props: Array<keyof T>) {
324324
Object.entries(entry).filter(([key, _]) => !props.includes(key as keyof T))
325325
);
326326
}
327+
328+
/** Returns the center x/y coordinate of a given element. */
329+
export function getCenterPoint(element: Element) {
330+
const { left, top, width, height } = element.getBoundingClientRect();
331+
332+
return {
333+
x: left + width * 0.5,
334+
y: top + height * 0.5,
335+
};
336+
}

src/components/resize-container/auto-scroll.ts

Lines changed: 0 additions & 52 deletions
This file was deleted.

src/components/resize-container/resize-container.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import type {
1818
ResizeMode,
1919
ResizeState,
2020
} from './types.js';
21-
// import { addScrollBehavior } from './auto-scroll.js';
2221

2322
export interface IgcResizeContainerComponentEventMap {
2423
igcResizeStart: CustomEvent<ResizeCallbackParams>;
@@ -65,9 +64,6 @@ export default class IgcResizeContainerComponent extends EventEmitterMixin<
6564
bottom: createRef(),
6665
};
6766

68-
// REVIEW: Scroll behavior
69-
// private _scroll = addScrollBehavior(this);
70-
7167
@state()
7268
private _isActive = false;
7369

@@ -134,26 +130,6 @@ export default class IgcResizeContainerComponent extends EventEmitterMixin<
134130
}
135131
}
136132

137-
// REVIEW: Scroll behavior
138-
// private _scrollIfNeeded(params: ResizeCallbackParams): void {
139-
// const {
140-
// state: {
141-
// initial: { bottom, right },
142-
// deltaX,
143-
// deltaY,
144-
// },
145-
// } = params;
146-
// const { x, y } = this._scroll.state;
147-
148-
// if (bottom + deltaX > x || right + deltaY > y) {
149-
// this._scroll.scrollBy({
150-
// left: deltaX * 2,
151-
// top: deltaY * 2,
152-
// behavior: 'smooth',
153-
// });
154-
// }
155-
// }
156-
157133
private _handlePointerEnter(): void {
158134
this._isActive = true;
159135
}
@@ -163,18 +139,12 @@ export default class IgcResizeContainerComponent extends EventEmitterMixin<
163139
}
164140

165141
private _handleResizeStart(params: ResizeCallbackParams): void {
166-
// REVIEW: Expose a property to control this behavior?
167-
params.event.preventDefault();
168-
169142
this.emitEvent('igcResizeStart', { bubbles: false, detail: params });
170-
// REVIEW: Scroll behavior
171-
// this._scroll.saveCurrentState();
172143
}
173144

174145
private _handleResize(params: ResizeCallbackParams): void {
175146
this._updateResizingState(params);
176147
this.emitEvent('igcResize', { bubbles: false, detail: params });
177-
// this._scrollIfNeeded(params);
178148
}
179149

180150
private _handleResizeEnd(params: ResizeCallbackParams): void {

src/components/tile-manager/position.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@ class TileDragStack {
9595
return last(this._stack).tile;
9696
}
9797

98-
public add(tile: IgcTileComponent): void {
98+
public pop(): TileDragStackEntry | undefined {
99+
return this._stack.pop();
100+
}
101+
102+
public push(tile: IgcTileComponent): void {
99103
this._stack.push({
100104
tile,
101105
position: tile.position,

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

Lines changed: 107 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import {
88
import { restore, spy, stub } from 'sinon';
99

1010
import { range } from 'lit/directives/range.js';
11+
import IgcIconButtonComponent from '../button/icon-button.js';
1112
import { escapeKey } from '../common/controllers/key-bindings.js';
1213
import { defineComponents } from '../common/definitions/defineComponents.js';
14+
import { getCenterPoint } from '../common/util.js';
1315
import {
1416
simulateClick,
1517
simulateKeyboard,
@@ -38,27 +40,32 @@ describe('Tile drag and drop', () => {
3840
return tileManager.tiles[index];
3941
}
4042

43+
function getTileContentContainer(element: IgcTileComponent) {
44+
return element.renderRoot.querySelector<HTMLDivElement>(
45+
'[part="content-container"]'
46+
)!;
47+
}
48+
49+
function getActionButtons(tile: IgcTileComponent) {
50+
return Array.from(
51+
tile.renderRoot
52+
.querySelector('[part="header"]')
53+
?.querySelectorAll(IgcIconButtonComponent.tagName) ?? []
54+
);
55+
}
56+
4157
async function dragAndDrop(tile: IgcTileComponent, target: IgcTileComponent) {
42-
const { top, left, width, height } = target.getBoundingClientRect();
58+
const { x, y } = getCenterPoint(target);
4359

4460
simulatePointerDown(tile);
45-
simulatePointerMove(tile, {
46-
clientX: left + width * 0.5,
47-
clientY: top + height * 0.5,
48-
});
61+
simulatePointerMove(tile, { clientX: x, clientY: y });
4962

5063
await viewTransitionComplete();
5164

5265
simulateLostPointerCapture(tile);
5366
await elementUpdated(tileManager);
5467
}
5568

56-
function getTileContentContainer(element: IgcTileComponent) {
57-
return element.renderRoot.querySelector<HTMLDivElement>(
58-
'[part="content-container"]'
59-
)!;
60-
}
61-
6269
function createTileManager(mode: TileManagerDragMode = 'none') {
6370
const result = Array.from(range(5)).map(
6471
(i) => html`
@@ -154,16 +161,13 @@ describe('Tile drag and drop', () => {
154161
it('should cancel dragging with Escape', async () => {
155162
const draggedTile = getTile(0);
156163
const dropTarget = getTile(4);
157-
const dropTargetRect = dropTarget.getBoundingClientRect();
164+
const { x, y } = getCenterPoint(dropTarget);
158165

159166
expect(draggedTile.position).to.equal(0);
160167
expect(dropTarget.position).to.equal(4);
161168

162169
simulatePointerDown(draggedTile);
163-
simulatePointerMove(draggedTile, {
164-
clientX: dropTargetRect.left + dropTargetRect.width * 0.5,
165-
clientY: dropTargetRect.top + dropTargetRect.height * 0.5,
166-
});
170+
simulatePointerMove(draggedTile, { clientX: x, clientY: y });
167171

168172
await viewTransitionComplete();
169173
expect(draggedTile.position).to.equal(4);
@@ -253,12 +257,10 @@ describe('Tile drag and drop', () => {
253257
expect(tileManager.tiles[1].id).to.equal('tile1');
254258
});
255259

256-
// REVIEW
257260
it('should swap positions only once while dragging smaller tile over bigger tile when using slide action', async () => {
258261
tileManager.columnCount = 5;
259262
const draggedTile = getTile(0);
260263
const dropTarget = getTile(1);
261-
const dropTargetRect = dropTarget.getBoundingClientRect();
262264

263265
draggedTile.rowSpan = 1;
264266
draggedTile.colSpan = 1;
@@ -267,22 +269,24 @@ describe('Tile drag and drop', () => {
267269
dropTarget.colSpan = 3;
268270
await elementUpdated(tileManager);
269271

272+
const { x, y } = getCenterPoint(dropTarget);
273+
270274
simulatePointerDown(draggedTile);
271-
simulatePointerMove(draggedTile, {
272-
clientX: dropTargetRect.left + dropTargetRect.width * 0.5,
273-
clientY: dropTargetRect.top + dropTargetRect.height * 0.5,
274-
});
275+
simulatePointerMove(draggedTile, { clientX: x, clientY: y });
275276
await viewTransitionComplete();
276277

277278
// Simulate second dragover event (inside dropTarget bounds)
278279
simulatePointerMove(draggedTile, {
279-
clientX: dropTargetRect.left + dropTargetRect.width * 0.5 + 5,
280-
clientY: dropTargetRect.top + dropTargetRect.height * 0.5 + 5,
280+
clientX: x + 5,
281+
clientY: y + 5,
281282
});
282283
await viewTransitionComplete();
283284

284285
expect(draggedTile.position).to.equal(1);
285286
expect(dropTarget.position).to.equal(0);
287+
288+
simulateLostPointerCapture(draggedTile);
289+
await elementUpdated(draggedTile);
286290
});
287291
});
288292

@@ -299,13 +303,10 @@ describe('Tile drag and drop', () => {
299303
target: IgcTileComponent
300304
) {
301305
const header = tile.renderRoot.querySelector('[part="title"]')!;
302-
const { top, left, width, height } = target.getBoundingClientRect();
306+
const { x, y } = getCenterPoint(target);
303307

304308
simulatePointerDown(header);
305-
simulatePointerMove(tile, {
306-
clientX: left + width * 0.5,
307-
clientY: top + height * 0.5,
308-
});
309+
simulatePointerMove(tile, { clientX: x, clientY: y });
309310

310311
await viewTransitionComplete();
311312

@@ -342,14 +343,11 @@ describe('Tile drag and drop', () => {
342343
const eventSpy = spy(tileManager, 'emitEvent');
343344

344345
const contentContainer = getTileContentContainer(draggedTile);
345-
const dropTargetRect = dropTarget.getBoundingClientRect();
346+
const { x, y } = getCenterPoint(dropTarget);
346347

347348
simulatePointerDown(contentContainer);
348349

349-
simulatePointerMove(draggedTile, {
350-
clientX: dropTargetRect.left + dropTargetRect.width * 0.5,
351-
clientY: dropTargetRect.top + dropTargetRect.height * 0.5,
352-
});
350+
simulatePointerMove(draggedTile, { clientX: x, clientY: y });
353351
await viewTransitionComplete();
354352

355353
simulateLostPointerCapture(draggedTile);
@@ -368,6 +366,17 @@ describe('Tile drag and drop', () => {
368366
);
369367
});
370368

369+
function createSlottedActionTile() {
370+
const tile = document.createElement(IgcTileComponent.tagName);
371+
const button = document.createElement('button');
372+
373+
button.slot = 'actions';
374+
button.textContent = 'Custom action';
375+
tile.append(button);
376+
377+
return { tile, button };
378+
}
379+
371380
it('should disable drag and drop when tile is maximized', async () => {
372381
const eventSpy = spy(tileManager, 'emitEvent');
373382
const draggedTile = getTile(0);
@@ -429,5 +438,69 @@ describe('Tile drag and drop', () => {
429438

430439
restore();
431440
});
441+
442+
it('should not start a drag operation when interacting with the default tile actions in `tile-header` drag mode', async () => {
443+
tileManager.dragMode = 'tile-header';
444+
const tile = getTile(0);
445+
const [maximize, _] = getActionButtons(tile);
446+
const eventSpy = spy(tileManager, 'emitEvent');
447+
448+
maximize.click();
449+
await elementUpdated(tile);
450+
451+
expect(tile.maximized).to.be.true;
452+
expect(eventSpy).not.calledWith('igcTileDragStarted');
453+
454+
maximize.click();
455+
await elementUpdated(tile);
456+
457+
expect(tile.maximized).to.be.false;
458+
});
459+
460+
it('should not start a drag operation when interacting with the default tile actions in `tile` drag mode', async () => {
461+
tileManager.dragMode = 'tile';
462+
const tile = getTile(0);
463+
const [maximize, _] = getActionButtons(tile);
464+
const eventSpy = spy(tileManager, 'emitEvent');
465+
466+
maximize.click();
467+
await elementUpdated(tile);
468+
469+
expect(tile.maximized).to.be.true;
470+
expect(eventSpy).not.calledWith('igcTileDragStarted');
471+
472+
maximize.click();
473+
await elementUpdated(tile);
474+
475+
expect(tile.maximized).to.be.false;
476+
});
477+
478+
it('should not start a drag operation when interacting with the slotted tile actions in `tile-header` drag mode', async () => {
479+
const eventSpy = spy(tileManager, 'emitEvent');
480+
const { tile, button } = createSlottedActionTile();
481+
482+
tileManager.dragMode = 'tile-header';
483+
tileManager.append(tile);
484+
await elementUpdated(tileManager);
485+
486+
button.click();
487+
await elementUpdated(tile);
488+
489+
expect(eventSpy).not.calledWith('igcTileDragStarted');
490+
});
491+
492+
it('should not start a drag operation when interacting with the slotted tile actions in `tile` drag mode', async () => {
493+
const eventSpy = spy(tileManager, 'emitEvent');
494+
const { tile, button } = createSlottedActionTile();
495+
496+
tileManager.dragMode = 'tile';
497+
tileManager.append(tile);
498+
await elementUpdated(tileManager);
499+
500+
button.click();
501+
await elementUpdated(tile);
502+
503+
expect(eventSpy).not.calledWith('igcTileDragStarted');
504+
});
432505
});
433506
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ describe('Tile Manager component', () => {
156156
<header part="title">
157157
<slot name="title"></slot>
158158
</header>
159-
<section part="actions">
159+
<section id="tile-actions" part="actions">
160160
<slot name="maximize-action">
161161
<igc-icon-button
162162
aria-label="expand_content"

0 commit comments

Comments
 (0)