wally-ui 1.13.0 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { Component, computed } from '@angular/core';
|
|
1
|
+
import { Component, computed, effect, ElementRef, signal } from '@angular/core';
|
|
2
2
|
import { DropdownMenuSubService } from '../dropdown-menu-sub.service';
|
|
3
3
|
|
|
4
|
+
export type SubmenuPosition = 'right' | 'left' | 'bottom' | 'top';
|
|
5
|
+
|
|
4
6
|
@Component({
|
|
5
7
|
selector: 'wally-dropdown-menu-sub-content',
|
|
6
8
|
imports: [],
|
|
@@ -8,12 +10,119 @@ import { DropdownMenuSubService } from '../dropdown-menu-sub.service';
|
|
|
8
10
|
styleUrl: './dropdown-menu-sub-content.css'
|
|
9
11
|
})
|
|
10
12
|
export class DropdownMenuSubContent {
|
|
11
|
-
|
|
13
|
+
calculatedPosition = signal<SubmenuPosition>('right');
|
|
14
|
+
isPositioned = signal<boolean>(false);
|
|
12
15
|
|
|
13
16
|
positionClasses = computed(() => {
|
|
14
|
-
|
|
17
|
+
const position = this.calculatedPosition();
|
|
18
|
+
|
|
19
|
+
const positionMap = {
|
|
20
|
+
right: 'top-0 left-full ml-1',
|
|
21
|
+
left: 'top-0 right-full mr-1',
|
|
22
|
+
bottom: 'left-0 top-full mt-1',
|
|
23
|
+
top: 'left-0 bottom-full mb-1'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return positionMap[position];
|
|
15
27
|
});
|
|
16
28
|
|
|
29
|
+
constructor(
|
|
30
|
+
public subService: DropdownMenuSubService,
|
|
31
|
+
private elementRef: ElementRef
|
|
32
|
+
) {
|
|
33
|
+
effect(() => {
|
|
34
|
+
if (this.subService.isOpen()) {
|
|
35
|
+
this.isPositioned.set(false);
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
const bestPosition = this.calculateBestPosition();
|
|
38
|
+
this.calculatedPosition.set(bestPosition);
|
|
39
|
+
this.isPositioned.set(true);
|
|
40
|
+
}, 0);
|
|
41
|
+
} else {
|
|
42
|
+
this.isPositioned.set(false);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Measures available space around the trigger element in all directions.
|
|
49
|
+
* @returns Object containing trigger dimensions and available space, or null if trigger not found
|
|
50
|
+
*/
|
|
51
|
+
private measureAvailableSpace(): {
|
|
52
|
+
triggerRect: DOMRect;
|
|
53
|
+
spaceAbove: number;
|
|
54
|
+
spaceBelow: number;
|
|
55
|
+
spaceLeft: number;
|
|
56
|
+
spaceRight: number;
|
|
57
|
+
} | null {
|
|
58
|
+
const triggerElement = this.elementRef.nativeElement.parentElement;
|
|
59
|
+
|
|
60
|
+
if (!triggerElement) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const triggerRect = triggerElement.getBoundingClientRect();
|
|
65
|
+
const viewportWidth = window.innerWidth;
|
|
66
|
+
const viewportHeight = window.innerHeight;
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
triggerRect,
|
|
70
|
+
spaceAbove: triggerRect.top,
|
|
71
|
+
spaceBelow: viewportHeight - triggerRect.bottom,
|
|
72
|
+
spaceLeft: triggerRect.left,
|
|
73
|
+
spaceRight: viewportWidth - triggerRect.right
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Calculates the best position for the submenu based on available viewport space.
|
|
79
|
+
* Prioritizes right/left (horizontal) over top/bottom (vertical) for submenus.
|
|
80
|
+
* Always uses the same priority order: right → left → bottom → top
|
|
81
|
+
* @returns The optimal submenu position
|
|
82
|
+
*/
|
|
83
|
+
private calculateBestPosition(): SubmenuPosition {
|
|
84
|
+
const space = this.measureAvailableSpace();
|
|
85
|
+
|
|
86
|
+
if (!space) {
|
|
87
|
+
return 'right';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const menuDimensions = this.getMenuDimensions();
|
|
91
|
+
const MENU_MIN_HEIGHT = menuDimensions.height + 20;
|
|
92
|
+
const MENU_MIN_WIDTH = menuDimensions.width + 20;
|
|
93
|
+
|
|
94
|
+
// Always use same priority for submenus: right → left → bottom → top
|
|
95
|
+
if (space.spaceRight >= MENU_MIN_WIDTH) return 'right';
|
|
96
|
+
if (space.spaceLeft >= MENU_MIN_WIDTH) return 'left';
|
|
97
|
+
if (space.spaceBelow >= MENU_MIN_HEIGHT) return 'bottom';
|
|
98
|
+
return 'top';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Gets the submenu dimensions from the DOM.
|
|
103
|
+
* @returns Height and width of the submenu
|
|
104
|
+
*/
|
|
105
|
+
private getMenuDimensions(): {
|
|
106
|
+
height: number;
|
|
107
|
+
width: number;
|
|
108
|
+
} {
|
|
109
|
+
const menuElement = this.elementRef.nativeElement.querySelector('[role="menu"]');
|
|
110
|
+
|
|
111
|
+
if (!menuElement) {
|
|
112
|
+
return {
|
|
113
|
+
height: 200,
|
|
114
|
+
width: 224
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const rect = menuElement.getBoundingClientRect();
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
height: rect.height || 200,
|
|
122
|
+
width: rect.width || 224
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
17
126
|
onMouseEnter(): void {
|
|
18
127
|
this.subService.setHoveringContent(true);
|
|
19
128
|
this.subService.open();
|