Skip to content

Commit c3878e5

Browse files
committed
feat(tile-manager): Added transition for UI triggered maximized state changes
* Updated tests to wait for said transition when applicable * Changed icon service test to wait on macrotask queue since tests were flaky.
1 parent c8596c2 commit c3878e5

File tree

5 files changed

+105
-93
lines changed

5 files changed

+105
-93
lines changed

src/components/common/controllers/drag.ts

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ type State = {
2727
position: { x: number; y: number };
2828
offset: { x: number; y: number };
2929
pointerState: {
30-
initial: { initialX: number; initialY: number };
31-
current: { currentX: number; currentY: number };
30+
previous: { x: number; y: number };
31+
current: { x: number; y: number };
3232
direction: Direction;
3333
};
3434
};
@@ -169,30 +169,13 @@ class DragController implements ReactiveController {
169169
}
170170

171171
private get _stateParameters(): DragState {
172-
this._state.pointerState.direction = this._trackPointerMovement();
173-
174172
return {
175173
...this._state,
176174
ghost: this._ghost,
177175
element: this._matchedElement,
178176
};
179177
}
180178

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-
196179
constructor(
197180
host: ReactiveControllerHost & LitElement,
198181
options?: DragControllerConfiguration
@@ -298,17 +281,9 @@ class DragController implements ReactiveController {
298281
}
299282

300283
this._updatePosition(event);
284+
this._updatePointerState(event);
301285
this._updateMatcher(event);
302286

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-
312287
const parameters = {
313288
event,
314289
state: this._stateParameters,
@@ -360,8 +335,8 @@ class DragController implements ReactiveController {
360335
position,
361336
offset,
362337
pointerState: {
363-
initial: { initialX: clientX, initialY: clientY },
364-
current: { currentX: clientX, currentY: clientY },
338+
previous: { x: clientX, y: clientY },
339+
current: { x: clientX, y: clientY },
365340
direction: 'end',
366341
},
367342
};
@@ -437,6 +412,23 @@ class DragController implements ReactiveController {
437412
Object.assign(this._state.position, { x: posX, y: posY });
438413
}
439414

415+
private _updatePointerState({ clientX, clientY }: PointerEvent): void {
416+
const state = this._state.pointerState;
417+
418+
state.previous = state.current;
419+
state.current = { x: clientX, y: clientY };
420+
421+
const dx = state.current.x - state.previous.x;
422+
const dy = state.current.y - state.previous.y;
423+
424+
if (Math.abs(dx) >= Math.abs(dy)) {
425+
const swapHorizontal = isLTR(this._host) ? dx >= 0 : dx <= 0;
426+
state.direction = swapHorizontal ? 'end' : 'start';
427+
} else {
428+
state.direction = dy >= 0 ? 'bottom' : 'top';
429+
}
430+
}
431+
440432
private _assignPosition(element: HTMLElement): void {
441433
element.style.transform = `translate3d(${roundByDPR(this._state.position.x)}px,${roundByDPR(this._state.position.y)}px,0)`;
442434
}

src/components/icon/icon.spec.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
expect,
55
fixture,
66
html,
7-
nextFrame,
87
} from '@open-wc/testing';
98
import { stub } from 'sinon';
109

@@ -163,7 +162,7 @@ describe('Icon broadcast service', () => {
163162
const iconName = 'bug';
164163

165164
registerIconFromText(iconName, bugSvg, collectionName);
166-
await nextFrame();
165+
await aTimeout(0);
167166

168167
const { actionType, collections } = first(events).data;
169168
expect(actionType).to.equal(ActionType.RegisterIcon);
@@ -183,7 +182,7 @@ describe('Icon broadcast service', () => {
183182
for (const each of icons) {
184183
registerIconFromText(each[0], each[1], collectionName);
185184
}
186-
await nextFrame();
185+
await aTimeout(0);
187186

188187
expect(events).lengthOf(icons.length);
189188
for (const [idx, event] of events.entries()) {
@@ -207,7 +206,7 @@ describe('Icon broadcast service', () => {
207206
name: 'reference-test',
208207
collection: collectionName,
209208
});
210-
await nextFrame();
209+
await aTimeout(0);
211210

212211
const { actionType, collections, references } = last(events).data;
213212

@@ -227,7 +226,7 @@ describe('Icon broadcast service', () => {
227226
meta.name = 'reference-test';
228227
meta.collection = collectionName;
229228
setIconRef(refName, refCollectionName, meta);
230-
await nextFrame();
229+
await aTimeout(0);
231230

232231
const { actionType, collections, references } = last(events).data;
233232

@@ -250,7 +249,7 @@ describe('Icon broadcast service', () => {
250249
},
251250
overwrite: true,
252251
});
253-
await nextFrame();
252+
await aTimeout(0);
254253

255254
expect(events.length).to.equal(0);
256255
});
@@ -286,7 +285,7 @@ describe('Icon broadcast service', () => {
286285

287286
// a peer is requesting a state sync
288287
channel.postMessage({ actionType: ActionType.SyncState });
289-
await nextFrame();
288+
await aTimeout(0);
290289

291290
expect(events).lengthOf(2); // [ActionType.RegisterIcon, ActionType.SyncState]
292291

@@ -311,7 +310,7 @@ describe('Icon broadcast service', () => {
311310

312311
// a peer is requesting a state sync
313312
channel.postMessage({ actionType: ActionType.SyncState });
314-
await nextFrame();
313+
await aTimeout(0);
315314

316315
expect(events).lengthOf(1); // [ActionType.SyncState]
317316

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -529,14 +529,16 @@ describe('Tile drag and drop', () => {
529529
const [maximize, _] = getActionButtons(tile);
530530
const eventSpy = spy(tile, 'emitEvent');
531531

532+
// Wait for maximized transition trigger from UI
532533
maximize.click();
533-
await elementUpdated(tile);
534+
await viewTransitionComplete();
534535

535536
expect(tile.maximized).to.be.true;
536537
expect(eventSpy).not.calledWith('igcTileDragStart');
537538

539+
// Wait for maximized transition trigger from UI
538540
maximize.click();
539-
await elementUpdated(tile);
541+
await viewTransitionComplete();
540542

541543
expect(tile.maximized).to.be.false;
542544
});
@@ -547,14 +549,16 @@ describe('Tile drag and drop', () => {
547549
const [maximize, _] = getActionButtons(tile);
548550
const eventSpy = spy(tile, 'emitEvent');
549551

552+
// Wait for maximized transition trigger from UI
550553
maximize.click();
551-
await elementUpdated(tile);
554+
await viewTransitionComplete();
552555

553556
expect(tile.maximized).to.be.true;
554557
expect(eventSpy).not.calledWith('igcTileDragStart');
555558

559+
// Wait for maximized transition trigger from UI
556560
maximize.click();
557-
await elementUpdated(tile);
561+
await viewTransitionComplete();
558562

559563
expect(tile.maximized).to.be.false;
560564
});

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
1+
import {
2+
elementUpdated,
3+
expect,
4+
fixture,
5+
html,
6+
nextFrame,
7+
} from '@open-wc/testing';
28
import { range } from 'lit/directives/range.js';
39
import { match, restore, spy, stub } from 'sinon';
410
import IgcIconButtonComponent from '../button/icon-button.js';
@@ -15,6 +21,11 @@ describe('Tile Manager component', () => {
1521

1622
let tileManager: IgcTileManagerComponent;
1723

24+
async function viewTransitionComplete() {
25+
await nextFrame();
26+
await nextFrame();
27+
}
28+
1829
function getTileManagerBase() {
1930
return tileManager.renderRoot.querySelector<HTMLElement>('[part="base"]')!;
2031
}
@@ -635,18 +646,19 @@ describe('Tile Manager component', () => {
635646
const eventSpy = spy(tile, 'emitEvent');
636647
const btnMaximize = getActionButtons(tile)[0];
637648

649+
// Wait for maximized transition trigger from UI
638650
simulateClick(btnMaximize);
639-
await elementUpdated(tile);
640-
await elementUpdated(tileManager);
651+
await viewTransitionComplete();
641652

642653
expect(eventSpy).calledWith('igcTileMaximize', {
643654
detail: { tile: tile, state: true },
644655
cancelable: true,
645656
});
646657
expect(tile.maximized).to.be.true;
647658

659+
// Wait for maximized transition trigger from UI
648660
simulateClick(btnMaximize);
649-
await elementUpdated(tileManager);
661+
await viewTransitionComplete();
650662

651663
expect(eventSpy).to.have.been.calledTwice;
652664
expect(eventSpy).calledWith('igcTileMaximize', {
@@ -678,8 +690,10 @@ describe('Tile Manager component', () => {
678690
const btnMaximize = getActionButtons(tile)[0];
679691

680692
expect(btnMaximize.name).equals('expand_content');
693+
694+
// Wait for maximized transition trigger from UI
681695
simulateClick(btnMaximize);
682-
await elementUpdated(tileManager);
696+
await viewTransitionComplete();
683697

684698
expect(btnMaximize.name).equals('collapse_content');
685699

src/components/tile-manager/tile.ts

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ 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;
139138

140139
private _customAdorners = new Map<string, boolean>(
141140
Object.entries({
@@ -400,58 +399,20 @@ export default class IgcTileComponent extends EventEmitterMixin<
400399
return true;
401400
}
402401

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

406405
if (this._dragStack.peek() === match) {
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-
}
437-
438-
if (shouldSwap) {
406+
if (this._shouldSwap(event, state, match)) {
439407
this._dragStack.pop();
440408
this._dragStack.push(match);
441-
442-
startViewTransition(() => {
443-
swapTiles(this, match);
444-
});
409+
this._performSwap(match);
445410
}
446-
447411
return;
448412
}
449413

450414
this._dragStack.push(match);
451-
452-
startViewTransition(() => {
453-
swapTiles(this, match);
454-
});
415+
this._performSwap(match);
455416
}
456417

457418
private _handleDragCancel() {
@@ -471,6 +432,42 @@ export default class IgcTileComponent extends EventEmitterMixin<
471432
this.emitEvent('igcTileDragEnd', { detail: this });
472433
}
473434

435+
private _performSwap(match: IgcTileComponent): void {
436+
startViewTransition(() => swapTiles(this, match));
437+
}
438+
439+
private _shouldSwap(
440+
{ clientX, clientY }: PointerEvent,
441+
state: DragCallbackParameters['state'],
442+
match: IgcTileComponent
443+
): boolean {
444+
const LTR = isLTR(this);
445+
const direction = state.pointerState.direction;
446+
447+
const { left, top, width, height } = match.getBoundingClientRect();
448+
const relativeX = (clientX - left) / width;
449+
const relativeY = (clientY - top) / height;
450+
451+
switch (direction) {
452+
case 'start':
453+
return (
454+
this.position > match.position &&
455+
(LTR ? relativeX <= 0.25 : relativeX >= 0.75)
456+
);
457+
case 'end':
458+
return (
459+
this.position < match.position &&
460+
(LTR ? relativeX >= 0.75 : relativeX <= 0.25)
461+
);
462+
case 'top':
463+
return this.position > match.position && relativeY <= 0.25;
464+
case 'bottom':
465+
return this.position < match.position && relativeY >= 0.75;
466+
default:
467+
return false;
468+
}
469+
}
470+
474471
private _skipDrag(event: PointerEvent): boolean {
475472
if (this._maximized || this.fullscreen) {
476473
return true;
@@ -562,12 +559,18 @@ export default class IgcTileComponent extends EventEmitterMixin<
562559
this._fullscreenController.setState(!this.fullscreen);
563560
}
564561

565-
private _handleMaximize() {
562+
private async _handleMaximize() {
566563
if (!this._emitMaximizedEvent()) {
567564
return;
568565
}
569566

570-
this.maximized = !this.maximized;
567+
this.style.zIndex = '1';
568+
569+
await startViewTransition(() => {
570+
this.maximized = !this.maximized;
571+
}).transition?.finished;
572+
573+
this.style.zIndex = '';
571574
}
572575

573576
private _emitFullScreenEvent(state: boolean) {

0 commit comments

Comments
 (0)