wally-ui 1.12.1 → 1.13.0
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/dist/cli.js +8 -5
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/playground/showcase/src/app/app.routes.server.ts +4 -0
- package/playground/showcase/src/app/components/ai/ai-composer/ai-composer.html +164 -31
- package/playground/showcase/src/app/components/ai/ai-composer/ai-composer.ts +25 -3
- package/playground/showcase/src/app/components/ai/ai-prompt-input/ai-prompt-input.html +1 -1
- package/playground/showcase/src/app/components/badge/badge.css +0 -0
- package/playground/showcase/src/app/components/badge/badge.html +3 -0
- package/playground/showcase/src/app/components/badge/badge.ts +24 -0
- package/playground/showcase/src/app/components/button/button.html +1 -3
- package/playground/showcase/src/app/components/button/button.ts +4 -4
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-content/dropdown-menu-content.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-content/dropdown-menu-content.html +9 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-content/dropdown-menu-content.spec.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-content/dropdown-menu-content.ts +167 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-group/dropdown-menu-group.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-group/dropdown-menu-group.html +5 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-group/dropdown-menu-group.spec.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-group/dropdown-menu-group.ts +10 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-item/dropdown-menu-item.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-item/dropdown-menu-item.html +6 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-item/dropdown-menu-item.spec.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-item/dropdown-menu-item.ts +37 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-label/dropdown-menu-label.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-label/dropdown-menu-label.html +3 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-label/dropdown-menu-label.spec.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-label/dropdown-menu-label.ts +11 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-portal/dropdown-menu-portal.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-portal/dropdown-menu-portal.html +1 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-portal/dropdown-menu-portal.spec.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-portal/dropdown-menu-portal.ts +11 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-separator/dropdown-menu-separator.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-separator/dropdown-menu-separator.html +1 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-separator/dropdown-menu-separator.spec.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-separator/dropdown-menu-separator.ts +11 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub/dropdown-menu-sub.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub/dropdown-menu-sub.html +3 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub/dropdown-menu-sub.spec.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub/dropdown-menu-sub.ts +16 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub-content/dropdown-menu-sub-content.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub-content/dropdown-menu-sub-content.html +9 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub-content/dropdown-menu-sub-content.spec.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub-content/dropdown-menu-sub-content.ts +31 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub-trigger/dropdown-menu-sub-trigger.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub-trigger/dropdown-menu-sub-trigger.html +13 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub-trigger/dropdown-menu-sub-trigger.spec.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub-trigger/dropdown-menu-sub-trigger.ts +40 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub.service.spec.ts +16 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub.service.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-trigger/dropdown-menu-trigger.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-trigger/dropdown-menu-trigger.html +8 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-trigger/dropdown-menu-trigger.spec.ts +23 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-trigger/dropdown-menu-trigger.ts +55 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu.css +0 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu.html +3 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu.service.spec.ts +16 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu.service.ts +31 -0
- package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu.ts +69 -0
- package/playground/showcase/src/app/components/tooltip/tooltip.ts +195 -80
- package/playground/showcase/src/app/pages/documentation/components/components.html +110 -51
- package/playground/showcase/src/app/pages/documentation/components/components.routes.ts +4 -0
- package/playground/showcase/src/app/pages/documentation/components/dropdown-menu-docs/dropdown-menu-docs.css +1 -0
- package/playground/showcase/src/app/pages/documentation/components/dropdown-menu-docs/dropdown-menu-docs.examples.ts +404 -0
- package/playground/showcase/src/app/pages/documentation/components/dropdown-menu-docs/dropdown-menu-docs.html +612 -0
- package/playground/showcase/src/app/pages/documentation/components/dropdown-menu-docs/dropdown-menu-docs.ts +127 -0
- package/playground/showcase/src/app/pages/home/home.html +10 -6
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Component, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'wally-dropdown-menu-group',
|
|
5
|
+
templateUrl: './dropdown-menu-group.html',
|
|
6
|
+
styleUrl: './dropdown-menu-group.css'
|
|
7
|
+
})
|
|
8
|
+
export class DropdownMenuGroup {
|
|
9
|
+
ariaLabel = input<string>('');
|
|
10
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<div (click)="handleClick($event)" class="p-1" role="menuitem" tabindex="0">
|
|
2
|
+
<div class="text-base text-[#0a0a0a] hover:bg-neutral-100 dark:hover:bg-neutral-700/50 dark:text-white py-2 px-4 rounded-lg"
|
|
3
|
+
[ngClass]="{'text-neutral-400 dark:!text-white/50 pointer-events-none': disabled(), 'cursor-pointer': !disabled()}">
|
|
4
|
+
<ng-content></ng-content>
|
|
5
|
+
</div>
|
|
6
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuItem } from './dropdown-menu-item';
|
|
4
|
+
|
|
5
|
+
describe('DropdownMenuItem', () => {
|
|
6
|
+
let component: DropdownMenuItem;
|
|
7
|
+
let fixture: ComponentFixture<DropdownMenuItem>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [DropdownMenuItem]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(DropdownMenuItem);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Component, input, InputSignal, output, OutputEmitterRef } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuService } from '../dropdown-menu.service';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'wally-dropdown-menu-item',
|
|
8
|
+
imports: [
|
|
9
|
+
CommonModule
|
|
10
|
+
],
|
|
11
|
+
templateUrl: './dropdown-menu-item.html',
|
|
12
|
+
styleUrl: './dropdown-menu-item.css'
|
|
13
|
+
})
|
|
14
|
+
export class DropdownMenuItem {
|
|
15
|
+
disabled: InputSignal<boolean> = input<boolean>(false);
|
|
16
|
+
|
|
17
|
+
click: OutputEmitterRef<void> = output<void>();
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private dropdownMenuService: DropdownMenuService
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Handles click event on menu item.
|
|
25
|
+
* Stops event propagation to prevent double-firing, emits click event, and closes dropdown.
|
|
26
|
+
* Does nothing if item is disabled.
|
|
27
|
+
*/
|
|
28
|
+
handleClick(event: MouseEvent): void {
|
|
29
|
+
if (this.disabled()) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
event?.stopPropagation();
|
|
34
|
+
this.click.emit();
|
|
35
|
+
this.dropdownMenuService.close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuLabel } from './dropdown-menu-label';
|
|
4
|
+
|
|
5
|
+
describe('DropdownMenuLabel', () => {
|
|
6
|
+
let component: DropdownMenuLabel;
|
|
7
|
+
let fixture: ComponentFixture<DropdownMenuLabel>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [DropdownMenuLabel]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(DropdownMenuLabel);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p>dropdown-menu-portal works!</p>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuPortal } from './dropdown-menu-portal';
|
|
4
|
+
|
|
5
|
+
describe('DropdownMenuPortal', () => {
|
|
6
|
+
let component: DropdownMenuPortal;
|
|
7
|
+
let fixture: ComponentFixture<DropdownMenuPortal>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [DropdownMenuPortal]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(DropdownMenuPortal);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="h-px bg-neutral-300 dark:bg-neutral-700"></div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuSeparator } from './dropdown-menu-separator';
|
|
4
|
+
|
|
5
|
+
describe('DropdownMenuSeparator', () => {
|
|
6
|
+
let component: DropdownMenuSeparator;
|
|
7
|
+
let fixture: ComponentFixture<DropdownMenuSeparator>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [DropdownMenuSeparator]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(DropdownMenuSeparator);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'wally-dropdown-menu-separator',
|
|
5
|
+
imports: [],
|
|
6
|
+
templateUrl: './dropdown-menu-separator.html',
|
|
7
|
+
styleUrl: './dropdown-menu-separator.css'
|
|
8
|
+
})
|
|
9
|
+
export class DropdownMenuSeparator {
|
|
10
|
+
|
|
11
|
+
}
|
package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub/dropdown-menu-sub.css
ADDED
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuSub } from './dropdown-menu-sub';
|
|
4
|
+
|
|
5
|
+
describe('DropdownMenuSub', () => {
|
|
6
|
+
let component: DropdownMenuSub;
|
|
7
|
+
let fixture: ComponentFixture<DropdownMenuSub>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [DropdownMenuSub]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(DropdownMenuSub);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub/dropdown-menu-sub.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuSubService } from '../dropdown-menu-sub.service';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'wally-dropdown-menu-sub',
|
|
7
|
+
imports: [],
|
|
8
|
+
providers: [
|
|
9
|
+
DropdownMenuSubService
|
|
10
|
+
],
|
|
11
|
+
templateUrl: './dropdown-menu-sub.html',
|
|
12
|
+
styleUrl: './dropdown-menu-sub.css'
|
|
13
|
+
})
|
|
14
|
+
export class DropdownMenuSub {
|
|
15
|
+
|
|
16
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
@if (subService.isOpen()) {
|
|
2
|
+
<div
|
|
3
|
+
(mouseenter)="onMouseEnter()"
|
|
4
|
+
(mouseleave)="onMouseLeave()"
|
|
5
|
+
[class]="'absolute bg-white dark:bg-[#1b1b1b] rounded-xl shadow-2xl border border-neutral-300 dark:border-neutral-700 min-w-56 z-50 transition-all duration-200 ease-out ' + positionClasses()"
|
|
6
|
+
role="menu">
|
|
7
|
+
<ng-content></ng-content>
|
|
8
|
+
</div>
|
|
9
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuSubContent } from './dropdown-menu-sub-content';
|
|
4
|
+
|
|
5
|
+
describe('DropdownMenuSubContent', () => {
|
|
6
|
+
let component: DropdownMenuSubContent;
|
|
7
|
+
let fixture: ComponentFixture<DropdownMenuSubContent>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [DropdownMenuSubContent]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(DropdownMenuSubContent);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Component, computed } from '@angular/core';
|
|
2
|
+
import { DropdownMenuSubService } from '../dropdown-menu-sub.service';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'wally-dropdown-menu-sub-content',
|
|
6
|
+
imports: [],
|
|
7
|
+
templateUrl: './dropdown-menu-sub-content.html',
|
|
8
|
+
styleUrl: './dropdown-menu-sub-content.css'
|
|
9
|
+
})
|
|
10
|
+
export class DropdownMenuSubContent {
|
|
11
|
+
constructor(public subService: DropdownMenuSubService) {}
|
|
12
|
+
|
|
13
|
+
positionClasses = computed(() => {
|
|
14
|
+
return 'top-0 left-full';
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
onMouseEnter(): void {
|
|
18
|
+
this.subService.setHoveringContent(true);
|
|
19
|
+
this.subService.open();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onMouseLeave(): void {
|
|
23
|
+
this.subService.setHoveringContent(false);
|
|
24
|
+
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
if (!this.subService.isHoveringContent()) {
|
|
27
|
+
this.subService.close();
|
|
28
|
+
}
|
|
29
|
+
}, 100);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<div (mouseenter)="onMouseEnter()" (mouseleave)="onMouseLeave()" class="p-1" role="menuitem" [attr.aria-haspopup]="true"
|
|
2
|
+
[attr.aria-expanded]="subService.isOpen()">
|
|
3
|
+
|
|
4
|
+
<div
|
|
5
|
+
class="flex justify-center items-center text-base text-[#0a0a0a] hover:bg-neutral-100 dark:hover:bg-neutral-700/50 dark:text-white py-2 px-4 rounded-lg cursor-pointer">
|
|
6
|
+
<ng-content></ng-content>
|
|
7
|
+
|
|
8
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"
|
|
9
|
+
class="size-4 ml-auto text-neutral-500 dark:text-neutral-400">
|
|
10
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
|
11
|
+
</svg>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuSubTrigger } from './dropdown-menu-sub-trigger';
|
|
4
|
+
|
|
5
|
+
describe('DropdownMenuSubTrigger', () => {
|
|
6
|
+
let component: DropdownMenuSubTrigger;
|
|
7
|
+
let fixture: ComponentFixture<DropdownMenuSubTrigger>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [DropdownMenuSubTrigger]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(DropdownMenuSubTrigger);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { DropdownMenuSubService } from '../dropdown-menu-sub.service';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'wally-dropdown-menu-sub-trigger',
|
|
6
|
+
imports: [],
|
|
7
|
+
templateUrl: './dropdown-menu-sub-trigger.html',
|
|
8
|
+
styleUrl: './dropdown-menu-sub-trigger.css'
|
|
9
|
+
})
|
|
10
|
+
export class DropdownMenuSubTrigger {
|
|
11
|
+
private hoverTimeout: any = null;
|
|
12
|
+
|
|
13
|
+
constructor(public subService: DropdownMenuSubService) {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Opens submenu after 150ms delay to prevent accidental triggers.
|
|
17
|
+
*/
|
|
18
|
+
onMouseEnter(): void {
|
|
19
|
+
this.hoverTimeout = setTimeout(() => {
|
|
20
|
+
this.subService.open();
|
|
21
|
+
}, 150);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Closes submenu with 300ms delay, allowing user to move mouse to submenu content.
|
|
26
|
+
* Cancels pending open timeout if mouse leaves quickly.
|
|
27
|
+
*/
|
|
28
|
+
onMouseLeave(): void {
|
|
29
|
+
if (this.hoverTimeout) {
|
|
30
|
+
clearTimeout(this.hoverTimeout);
|
|
31
|
+
this.hoverTimeout = null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
if (!this.subService.isHoveringContent()) {
|
|
36
|
+
this.subService.close();
|
|
37
|
+
}
|
|
38
|
+
}, 300);
|
|
39
|
+
}
|
|
40
|
+
}
|
package/playground/showcase/src/app/components/dropdown-menu/dropdown-menu-sub.service.spec.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuSubService } from './dropdown-menu-sub.service';
|
|
4
|
+
|
|
5
|
+
describe('DropdownMenuSubService', () => {
|
|
6
|
+
let service: DropdownMenuSubService;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
TestBed.configureTestingModule({});
|
|
10
|
+
service = TestBed.inject(DropdownMenuSubService);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should be created', () => {
|
|
14
|
+
expect(service).toBeTruthy();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Injectable, signal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Injectable()
|
|
4
|
+
export class DropdownMenuSubService {
|
|
5
|
+
isOpen = signal(false);
|
|
6
|
+
private hoveringContent = signal(false);
|
|
7
|
+
|
|
8
|
+
open(): void {
|
|
9
|
+
this.isOpen.set(true);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
close(): void {
|
|
13
|
+
this.isOpen.set(false);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
setHoveringContent(value: boolean): void {
|
|
17
|
+
this.hoveringContent.set(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
isHoveringContent(): boolean {
|
|
21
|
+
return this.hoveringContent();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuTrigger } from './dropdown-menu-trigger';
|
|
4
|
+
|
|
5
|
+
describe('DropdownMenuTrigger', () => {
|
|
6
|
+
let component: DropdownMenuTrigger;
|
|
7
|
+
let fixture: ComponentFixture<DropdownMenuTrigger>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [DropdownMenuTrigger]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(DropdownMenuTrigger);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuService } from '../dropdown-menu.service';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'wally-dropdown-menu-trigger',
|
|
7
|
+
imports: [],
|
|
8
|
+
templateUrl: './dropdown-menu-trigger.html',
|
|
9
|
+
styleUrl: './dropdown-menu-trigger.css'
|
|
10
|
+
})
|
|
11
|
+
export class DropdownMenuTrigger {
|
|
12
|
+
private hoverTimeout: any = null;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
public dropdownMenuService: DropdownMenuService
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
toggle(): void {
|
|
19
|
+
if (this.dropdownMenuService.triggerMode() === 'click') {
|
|
20
|
+
this.dropdownMenuService.toggle();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Handles mouse enter event for hover mode.
|
|
26
|
+
* Opens the dropdown after a 200ms delay to prevent accidental triggers.
|
|
27
|
+
*/
|
|
28
|
+
onMouseEnter(): void {
|
|
29
|
+
if (this.dropdownMenuService.triggerMode() === 'hover') {
|
|
30
|
+
this.hoverTimeout = setTimeout(() => {
|
|
31
|
+
this.dropdownMenuService.open();
|
|
32
|
+
}, 200);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handles mouse leave event for hover mode.
|
|
38
|
+
* Cancels pending open timeout and closes dropdown after 100ms delay,
|
|
39
|
+
* allowing user to move mouse to dropdown content without it closing.
|
|
40
|
+
*/
|
|
41
|
+
onMouseLeave(): void {
|
|
42
|
+
if (this.dropdownMenuService.triggerMode() === 'hover') {
|
|
43
|
+
if (this.hoverTimeout) {
|
|
44
|
+
clearTimeout(this.hoverTimeout);
|
|
45
|
+
this.hoverTimeout = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
if (!this.dropdownMenuService.isHoveringContent()) {
|
|
50
|
+
this.dropdownMenuService.close();
|
|
51
|
+
}
|
|
52
|
+
}, 100);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuService } from './dropdown-menu.service';
|
|
4
|
+
|
|
5
|
+
describe('DropdownMenuService', () => {
|
|
6
|
+
let service: DropdownMenuService;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
TestBed.configureTestingModule({});
|
|
10
|
+
service = TestBed.inject(DropdownMenuService);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should be created', () => {
|
|
14
|
+
expect(service).toBeTruthy();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Injectable, signal, WritableSignal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export type TriggerMode = 'click' | 'hover';
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class DropdownMenuService {
|
|
7
|
+
isOpen: WritableSignal<boolean> = signal(false);
|
|
8
|
+
triggerMode = signal<TriggerMode>('click');
|
|
9
|
+
|
|
10
|
+
private hoveringContent = signal(false);
|
|
11
|
+
|
|
12
|
+
toggle(): void {
|
|
13
|
+
this.isOpen.update(value => !value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
open(): void {
|
|
17
|
+
this.isOpen.set(true);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
close(): void {
|
|
21
|
+
this.isOpen.set(false);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setHoveringContent(value: boolean) {
|
|
25
|
+
this.hoveringContent.set(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
isHoveringContent(): boolean {
|
|
29
|
+
return this.hoveringContent();
|
|
30
|
+
}
|
|
31
|
+
}
|