1 interface HandleDropdownParams {
5 onOpen?: Function | undefined;
6 onClose?: Function | undefined;
10 function positionMenu(menu: HTMLElement, toggle: HTMLElement, showAside: boolean) {
11 const toggleRect = toggle.getBoundingClientRect();
12 const menuBounds = menu.getBoundingClientRect();
14 menu.style.position = 'fixed';
17 let targetLeft = toggleRect.right;
18 const isRightOOB = toggleRect.right + menuBounds.width > window.innerWidth;
20 targetLeft = Math.max(toggleRect.left - menuBounds.width, 0);
23 menu.style.top = toggleRect.top + 'px';
24 menu.style.left = targetLeft + 'px';
26 const isRightOOB = toggleRect.left + menuBounds.width > window.innerWidth;
27 let targetLeft = toggleRect.left;
29 targetLeft = Math.max(toggleRect.right - menuBounds.width, 0);
32 menu.style.top = toggleRect.bottom + 'px';
33 menu.style.left = targetLeft + 'px';
37 export class DropDownManager {
39 protected dropdownOptions: WeakMap<HTMLElement, HandleDropdownParams> = new WeakMap();
40 protected openDropdowns: Set<HTMLElement> = new Set();
43 this.onMenuMouseOver = this.onMenuMouseOver.bind(this);
45 window.addEventListener('click', (event: MouseEvent) => {
46 const target = event.target as HTMLElement;
47 this.closeAllNotContainingElement(target);
51 protected closeAllNotContainingElement(element: HTMLElement): void {
52 for (const menu of this.openDropdowns) {
53 if (!menu.parentElement?.contains(element)) {
54 this.closeDropdown(menu);
59 protected onMenuMouseOver(event: MouseEvent): void {
60 const target = event.target as HTMLElement;
61 this.closeAllNotContainingElement(target);
65 * Close all open dropdowns.
67 public closeAll(): void {
68 for (const menu of this.openDropdowns) {
69 this.closeDropdown(menu);
73 protected closeDropdown(menu: HTMLElement): void {
75 menu.style.removeProperty('position');
76 menu.style.removeProperty('left');
77 menu.style.removeProperty('top');
79 this.openDropdowns.delete(menu);
80 menu.removeEventListener('mouseover', this.onMenuMouseOver);
82 const onClose = this.getOptions(menu).onClose;
88 protected openDropdown(menu: HTMLElement): void {
89 const {toggle, showAside, onOpen} = this.getOptions(menu);
91 positionMenu(menu, toggle, Boolean(showAside));
93 this.openDropdowns.add(menu);
94 menu.addEventListener('mouseover', this.onMenuMouseOver);
101 protected getOptions(menu: HTMLElement): HandleDropdownParams {
102 const options = this.dropdownOptions.get(menu);
104 throw new Error(`Can't find options for dropdown menu`);
111 * Add handling for a new dropdown.
113 public handle(options: HandleDropdownParams) {
114 const {menu, toggle, showOnHover} = options;
117 this.dropdownOptions.set(menu, options);
119 // Configure default events
120 const toggleShowing = (event: MouseEvent) => {
121 menu.hasAttribute('hidden') ? this.openDropdown(menu) : this.closeDropdown(menu);
123 toggle.addEventListener('click', toggleShowing);
125 toggle.addEventListener('mouseenter', () => {
126 this.openDropdown(menu);