Skip to content

Commit 9b0956a

Browse files
committed
fix(tile): Resize events sequence
1 parent 0fff076 commit 9b0956a

File tree

7 files changed

+145
-25
lines changed

7 files changed

+145
-25
lines changed

src/components/common/controllers/drag.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ class DragController implements ReactiveController {
245245

246246
const parameters = { event, state: this._stateParameters };
247247
if (this._options.start?.call(this._host, parameters) === false) {
248-
this._removeGhost();
248+
this.dispose();
249249
return;
250250
}
251251

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,14 @@ export default class IgcResizeContainerComponent extends EventEmitterMixin<
134134
this._isActive = false;
135135
}
136136

137-
private _handleResizeStart(params: ResizeCallbackParams): void {
137+
private _handleResizeStart(params: ResizeCallbackParams) {
138138
params.event.preventDefault();
139-
this.emitEvent('igcResizeStart', { bubbles: false, detail: params });
139+
140+
return this.emitEvent('igcResizeStart', {
141+
bubbles: false,
142+
cancelable: true,
143+
detail: params,
144+
});
140145
}
141146

142147
private _handleResize(params: ResizeCallbackParams): void {

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,13 @@ class ResizeController implements ReactiveController {
138138

139139
this._setInitialState(event);
140140
this._createGhostElement();
141+
const parameters = { event, state: this._stateParameters };
142+
143+
if (this._options.start?.call(this._host, parameters) === false) {
144+
this.dispose();
145+
return;
146+
}
141147

142-
this._options.start?.call(this._host, {
143-
event,
144-
state: this._stateParameters,
145-
});
146148
this._setResizeState();
147149
}
148150

@@ -164,8 +166,8 @@ class ResizeController implements ReactiveController {
164166

165167
this._options.end?.call(this._host, parameters);
166168
this._state.current = parameters.state.current;
167-
// TODO:
168-
// this._updatePosition(this._resizeTarget);
169+
170+
parameters.state.commit?.() ?? this._updatePosition(this._resizeTarget);
169171
this.dispose();
170172
}
171173

src/components/resize-container/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type ResizeState = {
1212
deltaY: number;
1313
ghost: HTMLElement | null;
1414
trigger: HTMLElement | null;
15+
commit?: () => unknown;
1516
};
1617

1718
export type ResizeCallbackParams = {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ describe('Tile drag and drop', () => {
181181
await dragAndDrop(tile, target);
182182

183183
expect(eventSpy).calledOnceWith('igcTileDragStart');
184+
expect(eventSpy).not.calledWith('igcTileDragEnd');
185+
expect(eventSpy.callCount).to.equal(1);
184186
});
185187

186188
it('should correctly fire `igcTileDragEnd` event', async () => {

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

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ describe('Tile resize', () => {
3636
let columnSize: number;
3737
let rowSize: number;
3838

39+
/** Wait tile dragging view transition(s) to complete. */
40+
async function viewTransitionComplete() {
41+
await nextFrame();
42+
await nextFrame();
43+
}
44+
3945
function getTiles() {
4046
return Array.from(tileManager.querySelectorAll(IgcTileComponent.tagName));
4147
}
@@ -389,6 +395,92 @@ describe('Tile resize', () => {
389395
assertRectsAreEqual(firstTile.getBoundingClientRect(), tileRect);
390396
});
391397

398+
it('should fire `igcTileResizeStart` when a resize operation begins', async () => {
399+
const DOM = getResizeContainerDOM(firstTile);
400+
const eventSpy = spy(firstTile, 'emitEvent');
401+
402+
simulatePointerDown(DOM.adorners.corner);
403+
await elementUpdated(firstTile);
404+
405+
expect(eventSpy).calledWith('igcTileResizeStart');
406+
});
407+
408+
it('should stop resize operations by canceling the `igcTileResizeStart` event', async () => {
409+
const DOM = getResizeContainerDOM(firstTile);
410+
const eventSpy = spy(firstTile, 'emitEvent');
411+
412+
firstTile.addEventListener('igcTileResizeStart', (event) =>
413+
event.preventDefault()
414+
);
415+
416+
simulatePointerDown(DOM.adorners.corner);
417+
await elementUpdated(firstTile);
418+
419+
expect(eventSpy).calledWith('igcTileResizeStart');
420+
421+
simulatePointerMove(DOM.adorners.corner);
422+
simulateLostPointerCapture(DOM.adorners.corner);
423+
await elementUpdated(firstTile);
424+
425+
expect(eventSpy.callCount).to.equal(1);
426+
expect(eventSpy).not.calledWith('igcTileResizeEnd');
427+
});
428+
429+
it('should fire `igcTileResizeEnd` when a resize operation is performed successfully', async () => {
430+
const DOM = getResizeContainerDOM(firstTile);
431+
const eventSpy = spy(firstTile, 'emitEvent');
432+
433+
const { colSpan: initialColumnSpan, rowSpan: initialRowSpan } = firstTile;
434+
435+
const tileRect = firstTile.getBoundingClientRect();
436+
437+
simulatePointerDown(DOM.adorners.corner);
438+
await elementUpdated(firstTile);
439+
440+
expect(eventSpy).calledWith('igcTileResizeStart');
441+
442+
simulatePointerMove(DOM.adorners.corner, {
443+
clientX: tileRect.right * 2,
444+
clientY: tileRect.bottom * 2,
445+
});
446+
simulateLostPointerCapture(DOM.adorners.corner);
447+
await viewTransitionComplete();
448+
449+
expect(eventSpy).calledWith('igcTileResizeEnd');
450+
expect(DOM.ghostElement).to.be.null;
451+
452+
const { colSpan, rowSpan } = firstTile;
453+
expect(initialColumnSpan).is.lessThan(colSpan);
454+
expect(initialRowSpan).is.lessThan(rowSpan);
455+
});
456+
457+
it('should fire `igcTileResizeCancel` when canceling a resize operation', async () => {
458+
const DOM = getResizeContainerDOM(firstTile);
459+
const eventSpy = spy(firstTile, 'emitEvent');
460+
461+
const tileRect = firstTile.getBoundingClientRect();
462+
463+
simulatePointerDown(DOM.adorners.corner);
464+
await elementUpdated(firstTile);
465+
466+
expect(eventSpy).calledWith('igcTileResizeStart');
467+
468+
simulatePointerMove(DOM.adorners.corner, {
469+
clientX: tileRect.right * 2,
470+
clientY: tileRect.bottom * 2,
471+
});
472+
await elementUpdated(firstTile);
473+
474+
expect(DOM.ghostElement).not.to.be.null;
475+
476+
simulateKeyboard(DOM.resizeElement, escapeKey);
477+
await elementUpdated(firstTile);
478+
479+
expect(eventSpy).calledWith('igcTileResizeCancel');
480+
expect(DOM.ghostElement).to.be.null;
481+
assertRectsAreEqual(firstTile.getBoundingClientRect(), tileRect);
482+
});
483+
392484
it('should disable igc-resize behavior when `disableResize` is true', async () => {
393485
const DOM = getTileDOM(firstTile);
394486

src/components/tile-manager/tile.ts

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ export interface IgcTileComponentEventMap {
5555
igcTileDragStart: CustomEvent<IgcTileComponent>;
5656
igcTileDragEnd: CustomEvent<IgcTileComponent>;
5757
igcTileDragCancel: CustomEvent<IgcTileComponent>;
58-
igcResizeStart: CustomEvent<IgcTileComponent>;
59-
igcResizeMove: CustomEvent<IgcTileComponent>;
60-
igcResizeEnd: CustomEvent<IgcTileComponent>;
58+
igcTileResizeStart: CustomEvent<IgcTileComponent>;
59+
igcTileResizeEnd: CustomEvent<IgcTileComponent>;
60+
igcTileResizeCancel: CustomEvent<IgcTileComponent>;
6161
}
6262

6363
/**
@@ -71,9 +71,9 @@ export interface IgcTileComponentEventMap {
7171
* @fires igcTileDragStart - Fired when a drag operation on a tile is about to begin. Cancelable.
7272
* @fires igcTileDragEnd - Fired when a drag operation with a tile is successfully completed.
7373
* @fires igcTileDragCancel - Fired when a tile drag operation is canceled by the user.
74-
* @fires igcResizeStart - Fired when tile begins resizing.
75-
* @fires igcResizeMove - Fired when tile is being resized.
76-
* @fires igcResizeEnd - Fired when tile finishes resizing.
74+
* @fires igcTileResizeStart - Fired when a resize operation on a tile is about to begin. Cancelable.
75+
* @fires igcTileResizeEnd - Fired when a resize operation on a tile is successfully completed.
76+
* @fires igcTileResizeCancel - Fired when a resize operation on a tile is canceled by the user.
7777
*
7878
* @slot title - Renders the title of the tile header.
7979
* @slot maximize-action - Renders the maximize action element.
@@ -466,10 +466,17 @@ export default class IgcTileComponent extends EventEmitterMixin<
466466
this.part.toggle('resizing', state);
467467
}
468468

469-
private _handleResizeStart({
470-
detail: { state },
471-
}: CustomEvent<ResizeCallbackParams>) {
472-
this._resizeState.updateState(state.initial, this, this._cssContainer);
469+
private _handleResizeStart(event: CustomEvent<ResizeCallbackParams>) {
470+
if (!this._emitTileResizeStart()) {
471+
event.preventDefault();
472+
return;
473+
}
474+
475+
this._resizeState.updateState(
476+
event.detail.state.initial,
477+
this,
478+
this._cssContainer
479+
);
473480
this._setResizeState();
474481
}
475482

@@ -492,23 +499,27 @@ export default class IgcTileComponent extends EventEmitterMixin<
492499
}
493500
}
494501

495-
private async _handleResizeEnd({
502+
private _handleResizeEnd({
496503
detail: { state },
497504
}: CustomEvent<ResizeCallbackParams>) {
498505
const { colSpan, rowSpan } = this._resizeState.calculateResizedGridPosition(
499506
state.current
500507
);
501508

502-
await startViewTransition(() => {
503-
this.colSpan = colSpan;
504-
this.rowSpan = rowSpan;
505-
}).transition?.updateCallbackDone;
509+
state.commit = async () => {
510+
await startViewTransition(() => {
511+
this.colSpan = colSpan;
512+
this.rowSpan = rowSpan;
513+
}).transition?.updateCallbackDone;
506514

507-
this._setResizeState(false);
515+
this._setResizeState(false);
516+
this.emitEvent('igcTileResizeEnd', { detail: this });
517+
};
508518
}
509519

510520
private _handleResizeCancel() {
511521
this._setResizeState(false);
522+
this.emitEvent('igcTileResizeCancel', { detail: this });
512523
}
513524

514525
private _handleFullscreen() {
@@ -546,6 +557,13 @@ export default class IgcTileComponent extends EventEmitterMixin<
546557
});
547558
}
548559

560+
private _emitTileResizeStart() {
561+
return this.emitEvent('igcTileResizeStart', {
562+
detail: this,
563+
cancelable: true,
564+
});
565+
}
566+
549567
protected _renderDefaultAction(type: 'maximize' | 'fullscreen') {
550568
const [icon, listener] =
551569
type === 'fullscreen'

0 commit comments

Comments
 (0)