Skip to content

Commit 7c365cb

Browse files
authored
HParams: Bug Fix - Ensure context menu always opens within the window (#6474)
## Motivation for features / changes It was possible to open a context menu which would overflow the page. This actually lead to a strange CLS issue which looked pretty bad. Now the custom_modal component will check the size of the content once it has rendered and adjust its position to ensure it fits in the page. ## Screenshots of UI changes (or N/A) The context menu never overflows the window ![35353f79-2987-455b-af30-d05e1e29cefa](https://github.com/tensorflow/tensorboard/assets/78179109/aa44934c-7d51-4154-9e83-33b67d8e01b3) This also works at the bottom of the page but it doesn't come across as well in a screenshot ![image](https://github.com/tensorflow/tensorboard/assets/78179109/0158dbbf-2507-4ed9-92ca-a4293f94887d)
1 parent 5004d78 commit 7c365cb

File tree

2 files changed

+80
-0
lines changed

2 files changed

+80
-0
lines changed

tensorboard/webapp/widgets/custom_modal/custom_modal_component.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export class CustomModalComponent implements OnInit {
6969
// Wait until after the next browser frame.
7070
window.requestAnimationFrame(() => {
7171
if (visible) {
72+
this.ensureContentIsWithinWindow();
7273
this.onOpen.emit();
7374
} else {
7475
this.onClose.emit();
@@ -90,6 +91,29 @@ export class CustomModalComponent implements OnInit {
9091
document.addEventListener('click', this.clickListener);
9192
}
9293

94+
private ensureContentIsWithinWindow() {
95+
if (!this.content) {
96+
return;
97+
}
98+
99+
const boundingBox = this.content.nativeElement.getBoundingClientRect();
100+
if (boundingBox.left < 0) {
101+
this.content.nativeElement.style.left = 0;
102+
}
103+
if (boundingBox.left + boundingBox.width > window.innerWidth) {
104+
this.content.nativeElement.style.left =
105+
window.innerWidth - boundingBox.width + 'px';
106+
}
107+
108+
if (boundingBox.top < 0) {
109+
this.content.nativeElement.style.top = 0;
110+
}
111+
if (boundingBox.top + boundingBox.height > window.innerHeight) {
112+
this.content.nativeElement.style.top =
113+
window.innerHeight - boundingBox.height + 'px';
114+
}
115+
}
116+
93117
@HostListener('document:keydown.escape', ['$event'])
94118
public close() {
95119
document.removeEventListener('click', this.clickListener);

tensorboard/webapp/widgets/custom_modal/custom_modal_test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ See the License for the specific language governing permissions and
1313
limitations under the License.
1414
==============================================================================*/
1515
import {ComponentFixture, TestBed} from '@angular/core/testing';
16+
import {By} from '@angular/platform-browser';
1617
import {CustomModalComponent} from './custom_modal_component';
1718
import {CommonModule} from '@angular/common';
1819
import {
@@ -135,4 +136,59 @@ describe('custom modal', () => {
135136
expect(fixture.componentInstance.isOpen).toBeFalse();
136137
});
137138
});
139+
140+
describe('ensures content is always within the window', () => {
141+
beforeEach(() => {
142+
window.innerHeight = 1000;
143+
window.innerWidth = 1000;
144+
});
145+
146+
it('sets left to 0 if less than 0', async () => {
147+
const fixture = TestBed.createComponent(TestableComponent);
148+
fixture.detectChanges();
149+
fixture.componentInstance.openAtPosition({x: -10, y: 0});
150+
expect(fixture.componentInstance.isOpen).toBeFalse();
151+
await waitFrame();
152+
fixture.detectChanges();
153+
154+
const content = fixture.debugElement.query(By.css('.content'));
155+
expect(content.nativeElement.style.left).toEqual('0px');
156+
});
157+
158+
it('sets top to 0 if less than 0', async () => {
159+
const fixture = TestBed.createComponent(TestableComponent);
160+
fixture.detectChanges();
161+
fixture.componentInstance.openAtPosition({x: 0, y: -10});
162+
expect(fixture.componentInstance.isOpen).toBeFalse();
163+
await waitFrame();
164+
fixture.detectChanges();
165+
166+
const content = fixture.debugElement.query(By.css('.content'));
167+
expect(content.nativeElement.style.top).toEqual('0px');
168+
});
169+
170+
it('sets left to maximum if content overflows the window', async () => {
171+
const fixture = TestBed.createComponent(TestableComponent);
172+
fixture.detectChanges();
173+
fixture.componentInstance.openAtPosition({x: 1010, y: 0});
174+
expect(fixture.componentInstance.isOpen).toBeFalse();
175+
await waitFrame();
176+
fixture.detectChanges();
177+
const content = fixture.debugElement.query(By.css('.content'));
178+
// While rendering in a test the elements width and height will appear to be 0.
179+
expect(content.nativeElement.style.left).toEqual('1000px');
180+
});
181+
182+
it('sets top to maximum if content overflows the window', async () => {
183+
const fixture = TestBed.createComponent(TestableComponent);
184+
fixture.detectChanges();
185+
fixture.componentInstance.openAtPosition({x: 0, y: 1010});
186+
expect(fixture.componentInstance.isOpen).toBeFalse();
187+
await waitFrame();
188+
fixture.detectChanges();
189+
const content = fixture.debugElement.query(By.css('.content'));
190+
// While rendering in a test the elements width and height will appear to be 0.
191+
expect(content.nativeElement.style.top).toEqual('1000px');
192+
});
193+
});
138194
});

0 commit comments

Comments
 (0)