Skip to content

Commit 437ba64

Browse files
committed
fix: update fullscreen on esc key
1 parent 3d230dc commit 437ba64

File tree

4 files changed

+133
-48
lines changed

4 files changed

+133
-48
lines changed

src/components/common/utils.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ export function simulateDrop(node: Element) {
285285

286286
export function simulateDoubleClick(node: Element) {
287287
node.dispatchEvent(
288-
new MouseEvent('dblclick', { bubbles: true, composed: true })
288+
new PointerEvent('dblclick', { bubbles: true, composed: true })
289289
);
290290
}
291291

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

Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
22
import { range } from 'lit/directives/range.js';
3-
import { spy } from 'sinon';
3+
import { match, restore, spy, stub } from 'sinon';
44
import { defineComponents } from '../common/definitions/defineComponents.js';
55
import { first } from '../common/util.js';
6-
import { simulateDoubleClick } from '../common/utils.spec.js';
6+
import { simulateClick, simulateDoubleClick } from '../common/utils.spec.js';
77
import IgcTileHeaderComponent from './tile-header.js';
88
import IgcTileManagerComponent from './tile-manager.js';
99
import IgcTileComponent from './tile.js';
@@ -27,9 +27,9 @@ describe('Tile Manager component', () => {
2727
return Array.from(tileManager.querySelectorAll('igc-tile'));
2828
}
2929

30-
function getTileBaseWrapper(element: IgcTileComponent) {
31-
return element.renderRoot.querySelector<HTMLDivElement>('[part~="base"]')!;
32-
}
30+
// function getTileBaseWrapper(element: IgcTileComponent) {
31+
// return element.renderRoot.querySelector<HTMLDivElement>('[part~="base"]')!;
32+
// }
3333

3434
function createTileManager() {
3535
const result = Array.from(range(5)).map(
@@ -86,10 +86,10 @@ describe('Tile Manager component', () => {
8686
// TODO: Add an initialization test with non-defined column count and minimum dimension constraints
8787
it('is correctly initialized with its default component state', () => {
8888
// TODO: Add checks for other settings when implemented
89-
expect(tileManager.columnCount).to.equal(10);
89+
expect(tileManager.columnCount).to.equal(0);
9090
expect(tileManager.dragMode).to.equal('slide');
91-
expect(tileManager.minColumnWidth).to.equal('150px');
92-
expect(tileManager.minRowHeight).to.equal('200px');
91+
expect(tileManager.minColumnWidth).to.equal(undefined);
92+
expect(tileManager.minRowHeight).to.equal(undefined);
9393
expect(tileManager.tiles).lengthOf(2);
9494
});
9595

@@ -122,7 +122,7 @@ describe('Tile Manager component', () => {
122122
expect(tileManager).shadowDom.to.equal(
123123
`<div
124124
part="base"
125-
style="--ig-column-count:10;--ig-min-col-width:150px;--ig-min-row-height:200px;"
125+
style=""
126126
>
127127
<slot></slot>
128128
</div>`
@@ -293,45 +293,117 @@ describe('Tile Manager component', () => {
293293
});
294294

295295
describe('Tile state change behavior', () => {
296+
let tile: any;
297+
296298
beforeEach(async () => {
297299
tileManager = await fixture<IgcTileManagerComponent>(createTileManager());
300+
tile = first(tileManager.tiles);
301+
302+
// Mock `requestFullscreen`
303+
tile.requestFullscreen = stub().callsFake(() => {
304+
Object.defineProperty(document, 'fullscreenElement', {
305+
value: tile,
306+
configurable: true,
307+
});
308+
return Promise.resolve();
309+
});
310+
311+
// Mock `exitFullscreen`
312+
Object.defineProperty(document, 'exitFullscreen', {
313+
value: stub().callsFake(() => {
314+
Object.defineProperty(document, 'fullscreenElement', {
315+
value: null,
316+
configurable: true,
317+
});
318+
return Promise.resolve();
319+
}),
320+
configurable: true,
321+
});
298322
});
299323

300-
// TODO Mockup browser requestFullscreen/exitFullscreen
301-
xit('should correctly fire `igcTileFullscreen` event', async () => {
302-
const tile = first(tileManager.tiles);
303-
const tileWrapper = getTileBaseWrapper(tile);
324+
afterEach(() => {
325+
Object.defineProperty(document, 'fullscreenElement', {
326+
value: null,
327+
configurable: true,
328+
});
329+
330+
restore();
331+
});
332+
333+
it('should correctly change fullscreen state on double click', async () => {
334+
simulateDoubleClick(tile);
335+
await elementUpdated(tileManager);
336+
337+
expect(tile.requestFullscreen).to.have.been.calledOnce;
338+
expect(document.exitFullscreen).to.not.have.been.called;
339+
expect(tile.fullscreen).to.be.true;
340+
341+
simulateDoubleClick(tile);
342+
await elementUpdated(tileManager);
343+
344+
expect(document.exitFullscreen).to.have.been.calledOnce;
345+
expect(tile.fullscreen).to.be.false;
346+
});
304347

348+
it('should correctly fire `igcTileFullscreen` event', async () => {
349+
const tile = first(tileManager.tiles);
350+
const tileHeader = tile.querySelector('igc-tile-header');
351+
const fullscreenButton =
352+
tileHeader?.renderRoot.querySelectorAll('igc-icon-button')[1];
305353
const eventSpy = spy(tile, 'emitEvent');
306354

307-
simulateDoubleClick(tileWrapper);
308-
await elementUpdated(tile);
355+
simulateClick(fullscreenButton!);
356+
await elementUpdated(tileManager);
309357

310358
expect(eventSpy).calledWith('igcTileFullscreen', {
311359
detail: { tile: tile, state: true },
312360
cancelable: true,
313361
});
362+
expect(tile.fullscreen).to.be.true;
363+
364+
simulateClick(fullscreenButton!);
365+
await elementUpdated(tileManager);
314366

315-
// check if tile is fullscreen
367+
expect(eventSpy).calledWith('igcTileFullscreen', {
368+
detail: { tile: tile, state: false },
369+
cancelable: true,
370+
});
371+
expect(tile.fullscreen).to.be.false;
316372
});
317373

318-
// TODO Mockup browser requestFullscreen/exitFullscreen
319-
xit('can cancel `igcTileFullscreen` event', async () => {
320-
const tile = first(tileManager.tiles);
374+
it('can cancel `igcTileFullscreen` event', async () => {
321375
const eventSpy = spy(tile, 'emitEvent');
322376

323-
tile.addEventListener('igcTileFullscreen', (ev) => {
377+
tile.addEventListener('igcTileFullscreen', (ev: CustomEvent) => {
324378
ev.preventDefault();
325379
});
326380

327381
simulateDoubleClick(tile);
328382
await elementUpdated(tileManager);
329383

330-
expect(eventSpy).calledWith('igcTileFullscreen', {
331-
detail: { tile: tile, state: false },
332-
cancelable: true,
384+
expect(eventSpy).to.have.been.calledWith(
385+
'igcTileFullscreen',
386+
match({
387+
detail: { tile: tile, state: true },
388+
cancelable: true,
389+
})
390+
);
391+
expect(tile.fullscreen).to.be.false;
392+
expect(tile.requestFullscreen).not.to.have.been.called;
393+
});
394+
395+
it('should update fullscreen property on fullscreenchange (e.g. Esc key is pressed)', async () => {
396+
tile.fullscreen = true;
397+
398+
// Mock the browser removing fullscreen element and firing a fullscreenchange event
399+
Object.defineProperty(document, 'fullscreenElement', {
400+
configurable: true,
401+
value: null,
333402
});
334-
// check if tile is not fullscreen
403+
tile.dispatchEvent(new Event('fullscreenchange'));
404+
await elementUpdated(tileManager);
405+
406+
expect(tile.fullscreen).to.be.false;
335407
});
336408

337409
//TODO Fix test by selecting header icon and simulate click on it

src/components/tile-manager/tile.ts

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export default class IgcTileComponent extends EventEmitterMixin<
7272
private _position = -1;
7373
private _disableDrag = false;
7474
private _fullscreen = false;
75+
private _isUserTriggered = false;
7576
private _maximized = false;
7677
private _initialPointerX: number | null = null;
7778
private _initialPointerY: number | null = null;
@@ -162,8 +163,21 @@ export default class IgcTileComponent extends EventEmitterMixin<
162163
if (this._fullscreen === value) return;
163164

164165
this._fullscreen = value;
166+
167+
if (this._isUserTriggered && !this.emitFullScreenEvent()) {
168+
this._isUserTriggered = false;
169+
this._fullscreen = !value; // Rollback state if event is canceled
170+
return;
171+
}
172+
173+
if (this._fullscreen) {
174+
this.requestFullscreen();
175+
} else if (document.fullscreenElement) {
176+
document.exitFullscreen();
177+
}
178+
179+
this._isUserTriggered = false;
165180
this._context.setValue(this, true);
166-
this.handleFullscreenRequest();
167181
}
168182

169183
public get fullscreen() {
@@ -235,7 +249,8 @@ export default class IgcTileComponent extends EventEmitterMixin<
235249
// Will probably expose that as a dynamic binding based on a property
236250
// and as a response to some UI element interaction
237251
// REVIEW: fullscreen property and a tile header action button added
238-
this.addEventListener('dblclick', this.handleDoubleClick);
252+
this.addEventListener('dblclick', this.toggleFullscreen);
253+
this.addEventListener('fullscreenchange', this.handleFullscreenChange);
239254
}
240255

241256
public override connectedCallback() {
@@ -248,8 +263,17 @@ export default class IgcTileComponent extends EventEmitterMixin<
248263
}
249264

250265
public toggleFullscreen() {
266+
this._isUserTriggered = true;
251267
this.fullscreen = !this.fullscreen;
252-
this.emitFullScreenEvent();
268+
}
269+
270+
private handleFullscreenChange() {
271+
const isFullscreen = document.fullscreenElement === this;
272+
if (!isFullscreen && this._fullscreen) {
273+
// If exited fullscreen (e.g., via ESC key), update state
274+
this._isUserTriggered = true;
275+
this.fullscreen = false;
276+
}
253277
}
254278

255279
// protected override async firstUpdated() {
@@ -264,26 +288,6 @@ export default class IgcTileComponent extends EventEmitterMixin<
264288
});
265289
}
266290

267-
private handleDoubleClick() {
268-
if (!this.emitFullScreenEvent()) {
269-
return;
270-
}
271-
272-
this.fullscreen = !this.fullscreen;
273-
}
274-
275-
private async handleFullscreenRequest() {
276-
try {
277-
if (this.fullscreen) {
278-
await this.requestFullscreen();
279-
} else {
280-
await document.exitFullscreen();
281-
}
282-
} catch {
283-
document.exitFullscreen();
284-
}
285-
}
286-
287291
private handleDragStart(e: DragEvent) {
288292
const event = new CustomEvent('tileDragStart', {
289293
detail: { tile: this },

stories/tile-manager.stories.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ export const FinDashboard: Story = {
362362
<igc-button @click=${disableTileResize}>
363363
Toggle Tile 2 Resizing
364364
</igc-button>
365+
<igc-button @click=${toggleFullscreen}>
366+
Toggle Tile 1 Fullscreen prop
367+
</igc-button>
365368
`,
366369
};
367370

@@ -612,6 +615,12 @@ function disableTileResize() {
612615
tileManager.tiles[1].disableResize = !tileManager.tiles[1].disableResize;
613616
}
614617

618+
function toggleFullscreen() {
619+
const tileManager =
620+
document.querySelector<IgcTileManagerComponent>('igc-tile-manager')!;
621+
tileManager.tiles[1].fullscreen = !tileManager.tiles[1].fullscreen;
622+
}
623+
615624
function cancelStateChangeEvent(e: CustomEvent) {
616625
e.preventDefault();
617626
}

0 commit comments

Comments
 (0)