Skip to content

Commit ae2ac42

Browse files
fix: popover opening direction (#2022)
* Change popover opening direction based on available space below it * Update check * Use cacheable decorator
1 parent adb8b77 commit ae2ac42

File tree

3 files changed

+53
-4
lines changed

3 files changed

+53
-4
lines changed

src/components/ui/toolbox.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
100100
private static get CSS(): { [name: string]: string } {
101101
return {
102102
toolbox: 'ce-toolbox',
103+
toolboxOpenedTop: 'ce-toolbox--opened-top',
103104
};
104105
}
105106

@@ -198,8 +199,15 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
198199
return;
199200
}
200201

201-
this.popover.show();
202+
/**
203+
* Open popover top if there is not enought available space below it
204+
*/
205+
if (!this.shouldOpenPopoverBottom) {
206+
this.nodes.toolbox.style.setProperty('--popover-height', this.popover.calculateHeight() + 'px');
207+
this.nodes.toolbox.classList.add(Toolbox.CSS.toolboxOpenedTop);
208+
}
202209

210+
this.popover.show();
203211
this.opened = true;
204212
this.emit(ToolboxEvent.Opened);
205213
}
@@ -209,8 +217,8 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
209217
*/
210218
public close(): void {
211219
this.popover.hide();
212-
213220
this.opened = false;
221+
this.nodes.toolbox.classList.remove(Toolbox.CSS.toolboxOpenedTop);
214222
this.emit(ToolboxEvent.Closed);
215223
}
216224

@@ -225,6 +233,21 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
225233
}
226234
}
227235

236+
/**
237+
* Checks if there popover should be opened downwards.
238+
* It happens in case there is enough space below or not enough space above
239+
*/
240+
private get shouldOpenPopoverBottom(): boolean {
241+
const toolboxRect = this.nodes.toolbox.getBoundingClientRect();
242+
const editorElementRect = this.api.ui.nodes.redactor.getBoundingClientRect();
243+
const popoverHeight = this.popover.calculateHeight();
244+
const popoverPotentialBottomEdge = toolboxRect.top + popoverHeight;
245+
const popoverPotentialTopEdge = toolboxRect.top - popoverHeight;
246+
const bottomEdgeForComparison = Math.min(window.innerHeight, editorElementRect.bottom);
247+
248+
return popoverPotentialTopEdge < editorElementRect.top || popoverPotentialBottomEdge <= bottomEdgeForComparison;
249+
}
250+
228251
/**
229252
* Handles overlay click
230253
*/

src/components/utils/popover.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Listeners from './listeners';
33
import Flipper from '../flipper';
44
import SearchInput from './search-input';
55
import EventsDispatcher from './events';
6-
import { isMobileScreen, keyCodes } from '../utils';
6+
import { isMobileScreen, keyCodes, cacheable } from '../utils';
77

88
/**
99
* Describe parameters for rendering the single item of Popover
@@ -216,6 +216,26 @@ export default class Popover extends EventsDispatcher<PopoverEvent> {
216216
return this.flipper.hasFocus();
217217
}
218218

219+
/**
220+
* Helps to calculate height of popover while it is not displayed on screen.
221+
* Renders invisible clone of popover to get actual height.
222+
*/
223+
@cacheable
224+
public calculateHeight(): number {
225+
let height = 0;
226+
const popoverClone = this.nodes.popover.cloneNode(true) as HTMLElement;
227+
228+
popoverClone.style.visibility = 'hidden';
229+
popoverClone.style.position = 'absolute';
230+
popoverClone.style.top = '-1000px';
231+
popoverClone.classList.add(Popover.CSS.popoverOpened);
232+
document.body.appendChild(popoverClone);
233+
height = popoverClone.offsetHeight;
234+
popoverClone.remove();
235+
236+
return height;
237+
}
238+
219239
/**
220240
* Makes the UI
221241
*/

src/styles/toolbox.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
.ce-toolbox {
2+
--gap: 8px;
3+
24
@media (--not-mobile){
35
position: absolute;
4-
top: var(--toolbar-buttons-size);
6+
top: calc(var(--toolbox-buttons-size) + var(--gap));
57
left: 0;
8+
9+
&--opened-top {
10+
top: calc(-1 * (var(--gap) + var(--popover-height)));
11+
}
612
}
713
}
814

0 commit comments

Comments
 (0)