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,6 @@
1
1
  {
2
2
  "name": "wally-ui",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
4
4
  "description": "About Where’s Wally? Right here — bringing you ready-to-use Angular components with Wally-UI. Stop searching, start building.",
5
5
  "bin": {
6
6
  "wally": "dist/cli.js"
@@ -1,4 +1,4 @@
1
- @if (subService.isOpen()) {
1
+ @if (subService.isOpen() && isPositioned()) {
2
2
  <div
3
3
  (mouseenter)="onMouseEnter()"
4
4
  (mouseleave)="onMouseLeave()"
@@ -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
- constructor(public subService: DropdownMenuSubService) {}
13
+ calculatedPosition = signal<SubmenuPosition>('right');
14
+ isPositioned = signal<boolean>(false);
12
15
 
13
16
  positionClasses = computed(() => {
14
- return 'top-0 left-full';
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();