wally-ui 1.8.0 → 1.10.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/package.json +1 -1
- package/playground/showcase/src/app/components/button/button.html +11 -27
- package/playground/showcase/src/app/components/button/button.ts +3 -0
- package/playground/showcase/src/app/components/carousel/carousel.html +39 -25
- package/playground/showcase/src/app/components/carousel/carousel.ts +80 -104
- package/playground/showcase/src/app/pages/documentation/components/button-docs/button-docs.examples.ts +4 -0
- package/playground/showcase/src/app/pages/documentation/components/button-docs/button-docs.html +136 -55
- package/playground/showcase/src/app/pages/documentation/components/button-docs/button-docs.ts +4 -0
- package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.examples.ts +99 -15
- package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.html +475 -46
- package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.ts +13 -1
- package/playground/showcase/src/app/pages/documentation/documentation.html +83 -79
- package/playground/showcase/src/app/pages/documentation/documentation.ts +5 -1
- package/playground/showcase/src/app/pages/home/home.html +69 -45
- package/playground/showcase/src/app/pages/home/home.ts +2 -1
- package/playground/showcase/src/index.html +55 -4
package/package.json
CHANGED
|
@@ -1,28 +1,10 @@
|
|
|
1
|
-
<button
|
|
2
|
-
[
|
|
3
|
-
|
|
4
|
-
[
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
(click)="handleClick()"
|
|
9
|
-
class="
|
|
10
|
-
group
|
|
11
|
-
relative
|
|
12
|
-
w-full
|
|
13
|
-
flex items-center justify-center gap-2
|
|
14
|
-
text-white text-sm font-medium dark:text-[#0a0a0a]
|
|
15
|
-
bg-[#0a0a0a] hover:bg-[#0a0a0a]/85
|
|
16
|
-
disabled:bg-[#0a0a0a]/85
|
|
17
|
-
dark:bg-white dark:hover:bg-white/85
|
|
18
|
-
dark:disabled:bg-white/85
|
|
19
|
-
disabled:pointer-events-none
|
|
20
|
-
p-2.5
|
|
21
|
-
rounded-md
|
|
22
|
-
transition duration-300 ease-in-out
|
|
23
|
-
antialiased
|
|
24
|
-
cursor-pointer
|
|
25
|
-
">
|
|
1
|
+
<button [type]="type()" [disabled]="disabled() || loading()" [attr.aria-label]="ariaLabel() || null"
|
|
2
|
+
[attr.aria-describedby]="ariaDescribedBy() || null" [attr.aria-pressed]="ariaPressed()" [attr.aria-busy]="loading()"
|
|
3
|
+
(click)="handleClick()" class="group relative w-full flex items-center justify-center gap-2 text-sm font-medium disabled:pointer-events-none p-2.5 rounded-md transition duration-300 ease-in-out antialiased cursor-pointer
|
|
4
|
+
" [ngClass]="{
|
|
5
|
+
'text-white dark:text-[#0a0a0a] bg-[#0a0a0a] hover:bg-[#0a0a0a]/85 disabled:bg-[#0a0a0a]/85 dark:bg-white dark:hover:bg-white/85 dark:disabled:bg-white/85 dark:disabled:text-[#0a0a0a]/60': variant() === 'primary',
|
|
6
|
+
'text-[#0a0a0a] bg-neutral-200 hover:bg-neutral-200/60 disabled:bg-neutral-200/60 disabled:text-neutral-400 dark:text-white dark:bg-white/20 dark:hover:bg-white/10 dark:disabled:bg-white/5 dark:disabled:text-white/50': variant() === 'secondary',
|
|
7
|
+
}">
|
|
26
8
|
|
|
27
9
|
@if (showNotification()) {
|
|
28
10
|
<span class="absolute top-0 right-0 -mt-1 -mr-1 flex size-3">
|
|
@@ -34,8 +16,10 @@
|
|
|
34
16
|
}
|
|
35
17
|
|
|
36
18
|
@if (loading()) {
|
|
37
|
-
<svg class="size-4 animate-spin
|
|
38
|
-
|
|
19
|
+
<svg class="size-4 animate-spin" [ngClass]="{
|
|
20
|
+
'text-white dark:text-[#0a0a0a]': variant() === 'primary',
|
|
21
|
+
'dark:text-white': variant() === 'secondary',
|
|
22
|
+
}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
39
23
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
40
24
|
<path class="opacity-75" fill="currentColor"
|
|
41
25
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Component, input, InputSignal, output, OutputEmitterRef } from '@angular/core';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
|
|
4
|
+
type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive';
|
|
5
|
+
|
|
4
6
|
@Component({
|
|
5
7
|
selector: 'wally-button',
|
|
6
8
|
imports: [
|
|
@@ -14,6 +16,7 @@ export class Button {
|
|
|
14
16
|
disabled: InputSignal<boolean> = input<boolean>(false);
|
|
15
17
|
loading: InputSignal<boolean> = input<boolean>(false);
|
|
16
18
|
showNotification: InputSignal<boolean> = input<boolean>(false);
|
|
19
|
+
variant: InputSignal<ButtonVariant> = input<ButtonVariant>('primary');
|
|
17
20
|
|
|
18
21
|
// Accessibility properties
|
|
19
22
|
ariaLabel: InputSignal<string> = input<string>('');
|
|
@@ -1,32 +1,46 @@
|
|
|
1
|
-
<div class="w-full flex flex-col items-center gap-4">
|
|
2
|
-
<div class="
|
|
3
|
-
<
|
|
4
|
-
|
|
1
|
+
<div class="w-full h-full flex flex-col items-center gap-4 p-2">
|
|
2
|
+
<div class="w-full h-full flex items-center gap-3">
|
|
3
|
+
<div>
|
|
4
|
+
<button type="button"
|
|
5
|
+
class="group hidden sm:block bg-neutral-100 hover:bg-neutral-200 border-2 border-neutral-200 hover:border-neutral-300 dark:bg-[#1b1b1b] dark:hover:bg-[#1d1d1d] dark:border-neutral-700 hover:dark:border-neutral-600 p-2 rounded-full cursor-pointer transition-all duration-500 ease-in-out"
|
|
6
|
+
(click)="navigateToPreviousItem()" aria-label="Go to previous slide">
|
|
7
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
|
|
8
|
+
stroke="currentColor"
|
|
9
|
+
class="size-5 text-neutral-500 dark:text-neutral-300 group-hover:dark:text-white transition-all duration-500 ease-in-out">
|
|
10
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
|
11
|
+
</svg>
|
|
12
|
+
</button>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div class="relative w-full h-full overflow-hidden p-2 shadow rounded-lg" #carouselContainer>
|
|
16
|
+
<ng-content></ng-content>
|
|
17
|
+
</div>
|
|
5
18
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
[attr.aria-label]="'Navigate to slide ' + (dotIndex + 1)">
|
|
19
|
+
<div>
|
|
20
|
+
<button type="button"
|
|
21
|
+
class="group hidden sm:block bg-neutral-100 hover:bg-neutral-200 border-2 border-neutral-200 hover:border-neutral-300 dark:bg-[#1b1b1b] dark:hover:bg-[#1d1d1d] dark:border-neutral-700 hover:dark:border-neutral-600 p-2 rounded-full cursor-pointer transition-all duration-500 ease-in-out"
|
|
22
|
+
(click)="navigateToNextItem()" aria-label="Go to next slide">
|
|
23
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
class="size-5 text-neutral-500 dark:text-neutral-300 group-hover:dark:text-white transition-all duration-500 ease-in-out">
|
|
26
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
|
|
27
|
+
</svg>
|
|
16
28
|
</button>
|
|
17
|
-
|
|
29
|
+
</div>
|
|
18
30
|
</div>
|
|
19
31
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
@if (isNavigationIndicator()) {
|
|
33
|
+
<div class="flex items-center gap-2">
|
|
34
|
+
@for (navigationIndicator of navigationIndicatorsArray; track $index; let indicatorIndex = $index) {
|
|
35
|
+
<button type="button"
|
|
36
|
+
class="w-3.5 h-1.5 rounded-sm transition-all duration-500 ease-in-out cursor-pointer focus:outline-none focus:ring-0 active:outline-none"
|
|
37
|
+
[ngClass]="{
|
|
38
|
+
'bg-blue-500': indicatorIndex === currentVisibleItemIndex(),
|
|
39
|
+
'bg-neutral-300 hover:bg-neutral-400 hover:scale-105 dark:bg-neutral-700': indicatorIndex !== currentVisibleItemIndex()
|
|
40
|
+
}" (click)="navigateToSpecificItem(indicatorIndex)"
|
|
41
|
+
[attr.aria-label]="'Navigate to slide ' + (indicatorIndex + 1)">
|
|
30
42
|
</button>
|
|
43
|
+
}
|
|
31
44
|
</div>
|
|
45
|
+
}
|
|
32
46
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component,
|
|
1
|
+
import { Component, signal, ViewChild, ElementRef, AfterViewInit, Renderer2, OnDestroy, HostListener, WritableSignal, input, InputSignal } from '@angular/core';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
|
|
4
4
|
@Component({
|
|
@@ -6,14 +6,14 @@ import { CommonModule } from '@angular/common';
|
|
|
6
6
|
imports: [CommonModule],
|
|
7
7
|
templateUrl: './carousel.html',
|
|
8
8
|
})
|
|
9
|
-
export class Carousel implements
|
|
9
|
+
export class Carousel implements AfterViewInit, OnDestroy {
|
|
10
10
|
@ViewChild('carouselContainer', { static: false }) carouselContainer!: ElementRef;
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
isNavigationIndicator: InputSignal<boolean> = input<boolean>(false);
|
|
13
|
+
totalItemsCount: WritableSignal<number> = signal<number>(0);
|
|
14
|
+
currentVisibleItemIndex: WritableSignal<number> = signal<number>(0);
|
|
14
15
|
carouselItemElements: HTMLElement[] = [];
|
|
15
16
|
|
|
16
|
-
// Touch and swipe gesture state
|
|
17
17
|
private gestureState = {
|
|
18
18
|
startPositionX: 0,
|
|
19
19
|
startPositionY: 0,
|
|
@@ -23,22 +23,19 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
|
|
|
23
23
|
gestureStartTime: 0
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
private readonly
|
|
28
|
-
private readonly
|
|
29
|
-
private readonly MAXIMUM_SWIPE_DURATION = 300; // maximum milliseconds for swipe gesture
|
|
26
|
+
private readonly MINIMUM_SWIPE_DISTANCE = 50;
|
|
27
|
+
private readonly MINIMUM_SWIPE_VELOCITY = 0.3;
|
|
28
|
+
private readonly MAXIMUM_SWIPE_DURATION = 300;
|
|
30
29
|
|
|
31
|
-
// Event listener cleanup functions
|
|
32
30
|
private eventListenerCleanupFunctions: (() => void)[] = [];
|
|
33
31
|
|
|
34
|
-
get
|
|
32
|
+
get navigationIndicatorsArray() {
|
|
35
33
|
return Array(this.totalItemsCount()).fill(0);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
constructor(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
36
|
+
constructor(
|
|
37
|
+
private renderer: Renderer2
|
|
38
|
+
) { }
|
|
42
39
|
|
|
43
40
|
ngAfterViewInit(): void {
|
|
44
41
|
if (this.carouselContainer) {
|
|
@@ -49,16 +46,76 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
|
|
|
49
46
|
}
|
|
50
47
|
|
|
51
48
|
ngOnDestroy(): void {
|
|
52
|
-
// Clean up all event listeners to prevent memory leaks
|
|
53
49
|
this.eventListenerCleanupFunctions.forEach(cleanupFunction => cleanupFunction());
|
|
54
50
|
}
|
|
55
51
|
|
|
52
|
+
calculateNextItemIndex(currentItemIndex: number): number {
|
|
53
|
+
return (currentItemIndex + 1) % this.totalItemsCount();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
calculatePreviousItemIndex(currentItemIndex: number): number {
|
|
57
|
+
return (currentItemIndex - 1 + this.totalItemsCount()) % this.totalItemsCount();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
navigateToNextItem(): void {
|
|
61
|
+
this.currentVisibleItemIndex.set(this.calculateNextItemIndex(this.currentVisibleItemIndex()));
|
|
62
|
+
this.updateAllItemElementPositions();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
navigateToPreviousItem(): void {
|
|
66
|
+
this.currentVisibleItemIndex.set(this.calculatePreviousItemIndex(this.currentVisibleItemIndex()));
|
|
67
|
+
this.updateAllItemElementPositions();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
navigateToSpecificItem(targetItemIndex: number): void {
|
|
71
|
+
this.currentVisibleItemIndex.set(targetItemIndex);
|
|
72
|
+
this.updateAllItemElementPositions();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updateItemElementPosition(carouselItemElement: HTMLElement, itemIndex: number): void {
|
|
76
|
+
const currentVisibleIndex = this.currentVisibleItemIndex();
|
|
77
|
+
|
|
78
|
+
if (itemIndex === currentVisibleIndex) {
|
|
79
|
+
this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(0)');
|
|
80
|
+
} else if (itemIndex > currentVisibleIndex) {
|
|
81
|
+
this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(110%)');
|
|
82
|
+
} else {
|
|
83
|
+
this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(-110%)');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
updateAllItemElementPositions(): void {
|
|
88
|
+
this.carouselItemElements.forEach((itemElement, itemIndex) => {
|
|
89
|
+
this.updateItemElementPosition(itemElement, itemIndex);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@HostListener('keydown', ['$event'])
|
|
94
|
+
handleKeyboardNavigation(keyboardEvent: KeyboardEvent): void {
|
|
95
|
+
switch (keyboardEvent.key) {
|
|
96
|
+
case 'ArrowLeft':
|
|
97
|
+
keyboardEvent.preventDefault();
|
|
98
|
+
this.navigateToPreviousItem();
|
|
99
|
+
break;
|
|
100
|
+
case 'ArrowRight':
|
|
101
|
+
keyboardEvent.preventDefault();
|
|
102
|
+
this.navigateToNextItem();
|
|
103
|
+
break;
|
|
104
|
+
case 'Home':
|
|
105
|
+
keyboardEvent.preventDefault();
|
|
106
|
+
this.navigateToSpecificItem(0);
|
|
107
|
+
break;
|
|
108
|
+
case 'End':
|
|
109
|
+
keyboardEvent.preventDefault();
|
|
110
|
+
this.navigateToSpecificItem(this.totalItemsCount() - 1);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
56
115
|
private initializeCarouselItems(): void {
|
|
57
|
-
// Get all direct children elements of the carousel container
|
|
58
116
|
this.carouselItemElements = Array.from(this.carouselContainer.nativeElement.children);
|
|
59
117
|
this.totalItemsCount.set(this.carouselItemElements.length);
|
|
60
118
|
|
|
61
|
-
// Apply positioning and styling to each carousel item using Renderer2
|
|
62
119
|
this.carouselItemElements.forEach((itemElement, itemIndex) => {
|
|
63
120
|
this.renderer.setStyle(itemElement, 'position', 'absolute');
|
|
64
121
|
this.renderer.setStyle(itemElement, 'inset', '0');
|
|
@@ -73,18 +130,15 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
|
|
|
73
130
|
private setupTouchAndMouseEvents(): void {
|
|
74
131
|
const carouselContainerElement = this.carouselContainer.nativeElement;
|
|
75
132
|
|
|
76
|
-
// Touch events for mobile devices
|
|
77
133
|
const touchStartListener = this.renderer.listen(carouselContainerElement, 'touchstart', (event) => this.handleTouchStart(event));
|
|
78
134
|
const touchMoveListener = this.renderer.listen(carouselContainerElement, 'touchmove', (event) => this.handleTouchMove(event));
|
|
79
135
|
const touchEndListener = this.renderer.listen(carouselContainerElement, 'touchend', () => this.handleTouchEnd());
|
|
80
136
|
|
|
81
|
-
// Mouse events for desktop devices
|
|
82
137
|
const mouseDownListener = this.renderer.listen(carouselContainerElement, 'mousedown', (event) => this.handleMouseDown(event));
|
|
83
138
|
const mouseMoveListener = this.renderer.listen(carouselContainerElement, 'mousemove', (event) => this.handleMouseMove(event));
|
|
84
139
|
const mouseUpListener = this.renderer.listen(carouselContainerElement, 'mouseup', () => this.handleMouseUp());
|
|
85
140
|
const mouseLeaveListener = this.renderer.listen(carouselContainerElement, 'mouseleave', () => this.handleMouseUp());
|
|
86
141
|
|
|
87
|
-
// Store all cleanup functions for proper memory management
|
|
88
142
|
this.eventListenerCleanupFunctions.push(
|
|
89
143
|
touchStartListener,
|
|
90
144
|
touchMoveListener,
|
|
@@ -95,48 +149,24 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
|
|
|
95
149
|
mouseLeaveListener
|
|
96
150
|
);
|
|
97
151
|
|
|
98
|
-
// Optimize touch performance by restricting to horizontal panning only
|
|
99
152
|
this.renderer.setStyle(carouselContainerElement, 'touch-action', 'pan-x');
|
|
100
153
|
}
|
|
101
154
|
|
|
102
155
|
private setupAccessibilityAttributes(): void {
|
|
103
156
|
const carouselContainerElement = this.carouselContainer.nativeElement;
|
|
104
157
|
|
|
105
|
-
// Set ARIA attributes for screen readers and accessibility
|
|
106
158
|
this.renderer.setAttribute(carouselContainerElement, 'role', 'region');
|
|
107
159
|
this.renderer.setAttribute(carouselContainerElement, 'aria-label', 'Carousel');
|
|
108
160
|
this.renderer.setAttribute(carouselContainerElement, 'tabindex', '0');
|
|
109
161
|
}
|
|
110
162
|
|
|
111
|
-
updateItemElementPosition(carouselItemElement: HTMLElement, itemIndex: number): void {
|
|
112
|
-
const currentVisibleIndex = this.currentVisibleItemIndex();
|
|
113
|
-
|
|
114
|
-
if (itemIndex === currentVisibleIndex) {
|
|
115
|
-
// Position the currently visible item at center (0% translation)
|
|
116
|
-
this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(0)');
|
|
117
|
-
} else if (itemIndex > currentVisibleIndex) {
|
|
118
|
-
// Position items to the right of current item (110% translation to the right)
|
|
119
|
-
this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(110%)');
|
|
120
|
-
} else {
|
|
121
|
-
// Position items to the left of current item (110% translation to the left)
|
|
122
|
-
this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(-110%)');
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
updateAllItemElementPositions(): void {
|
|
127
|
-
this.carouselItemElements.forEach((itemElement, itemIndex) => {
|
|
128
|
-
this.updateItemElementPosition(itemElement, itemIndex);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Touch event handlers for mobile devices
|
|
133
163
|
private handleTouchStart(touchEvent: TouchEvent): void {
|
|
134
164
|
this.initializeGesture(touchEvent.touches[0].clientX, touchEvent.touches[0].clientY);
|
|
135
165
|
}
|
|
136
166
|
|
|
137
167
|
private handleTouchMove(touchEvent: TouchEvent): void {
|
|
138
168
|
if (this.gestureState.isCurrentlyDragging) {
|
|
139
|
-
touchEvent.preventDefault();
|
|
169
|
+
touchEvent.preventDefault();
|
|
140
170
|
this.updateGesturePosition(touchEvent.touches[0].clientX, touchEvent.touches[0].clientY);
|
|
141
171
|
}
|
|
142
172
|
}
|
|
@@ -145,14 +175,13 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
|
|
|
145
175
|
this.finalizeGestureAndNavigate();
|
|
146
176
|
}
|
|
147
177
|
|
|
148
|
-
// Mouse event handlers for desktop devices
|
|
149
178
|
private handleMouseDown(mouseEvent: MouseEvent): void {
|
|
150
179
|
this.initializeGesture(mouseEvent.clientX, mouseEvent.clientY);
|
|
151
180
|
}
|
|
152
181
|
|
|
153
182
|
private handleMouseMove(mouseEvent: MouseEvent): void {
|
|
154
183
|
if (this.gestureState.isCurrentlyDragging) {
|
|
155
|
-
mouseEvent.preventDefault();
|
|
184
|
+
mouseEvent.preventDefault();
|
|
156
185
|
this.updateGesturePosition(mouseEvent.clientX, mouseEvent.clientY);
|
|
157
186
|
}
|
|
158
187
|
}
|
|
@@ -161,7 +190,6 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
|
|
|
161
190
|
this.finalizeGestureAndNavigate();
|
|
162
191
|
}
|
|
163
192
|
|
|
164
|
-
// Unified gesture handling methods
|
|
165
193
|
private initializeGesture(xPosition: number, yPosition: number): void {
|
|
166
194
|
this.gestureState = {
|
|
167
195
|
startPositionX: xPosition,
|
|
@@ -188,70 +216,18 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
|
|
|
188
216
|
const gestureDuration = Date.now() - this.gestureState.gestureStartTime;
|
|
189
217
|
const gestureVelocity = Math.abs(horizontalDistanceMoved) / gestureDuration;
|
|
190
218
|
|
|
191
|
-
// Only process horizontal swipes (ignore vertical movements)
|
|
192
219
|
if (Math.abs(horizontalDistanceMoved) > Math.abs(verticalDistanceMoved)) {
|
|
193
|
-
// Check if gesture meets minimum requirements for navigation
|
|
194
220
|
const isValidSwipeGesture = Math.abs(horizontalDistanceMoved) > this.MINIMUM_SWIPE_DISTANCE ||
|
|
195
221
|
(gestureVelocity > this.MINIMUM_SWIPE_VELOCITY && gestureDuration < this.MAXIMUM_SWIPE_DURATION);
|
|
196
222
|
|
|
197
223
|
if (isValidSwipeGesture) {
|
|
198
224
|
if (horizontalDistanceMoved > 0) {
|
|
199
|
-
this.navigateToPreviousItem();
|
|
225
|
+
this.navigateToPreviousItem();
|
|
200
226
|
} else {
|
|
201
|
-
this.navigateToNextItem();
|
|
227
|
+
this.navigateToNextItem();
|
|
202
228
|
}
|
|
203
229
|
}
|
|
204
230
|
}
|
|
205
|
-
|
|
206
|
-
// Reset gesture state after processing
|
|
207
231
|
this.gestureState.isCurrentlyDragging = false;
|
|
208
232
|
}
|
|
209
|
-
|
|
210
|
-
// Keyboard navigation handler
|
|
211
|
-
@HostListener('keydown', ['$event'])
|
|
212
|
-
handleKeyboardNavigation(keyboardEvent: KeyboardEvent): void {
|
|
213
|
-
switch (keyboardEvent.key) {
|
|
214
|
-
case 'ArrowLeft':
|
|
215
|
-
keyboardEvent.preventDefault();
|
|
216
|
-
this.navigateToPreviousItem();
|
|
217
|
-
break;
|
|
218
|
-
case 'ArrowRight':
|
|
219
|
-
keyboardEvent.preventDefault();
|
|
220
|
-
this.navigateToNextItem();
|
|
221
|
-
break;
|
|
222
|
-
case 'Home':
|
|
223
|
-
keyboardEvent.preventDefault();
|
|
224
|
-
this.navigateToSpecificItem(0);
|
|
225
|
-
break;
|
|
226
|
-
case 'End':
|
|
227
|
-
keyboardEvent.preventDefault();
|
|
228
|
-
this.navigateToSpecificItem(this.totalItemsCount() - 1);
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Circular buffer algorithm implementation for infinite carousel navigation
|
|
234
|
-
calculateNextItemIndex(currentItemIndex: number): number {
|
|
235
|
-
return (currentItemIndex + 1) % this.totalItemsCount();
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
calculatePreviousItemIndex(currentItemIndex: number): number {
|
|
239
|
-
return (currentItemIndex - 1 + this.totalItemsCount()) % this.totalItemsCount();
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
navigateToNextItem(): void {
|
|
243
|
-
this.currentVisibleItemIndex.set(this.calculateNextItemIndex(this.currentVisibleItemIndex()));
|
|
244
|
-
this.updateAllItemElementPositions();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
navigateToPreviousItem(): void {
|
|
248
|
-
this.currentVisibleItemIndex.set(this.calculatePreviousItemIndex(this.currentVisibleItemIndex()));
|
|
249
|
-
this.updateAllItemElementPositions();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
navigateToSpecificItem(targetItemIndex: number): void {
|
|
253
|
-
this.currentVisibleItemIndex.set(targetItemIndex);
|
|
254
|
-
this.updateAllItemElementPositions();
|
|
255
|
-
}
|
|
256
|
-
// end test Circular Buffer ---
|
|
257
|
-
}
|
|
233
|
+
}
|
|
@@ -15,6 +15,10 @@ export const ButtonCodeExamples = {
|
|
|
15
15
|
// Basic usage
|
|
16
16
|
basicUsage: `<wally-button>Wally Button</wally-button>`,
|
|
17
17
|
|
|
18
|
+
// Variants
|
|
19
|
+
primaryVariant: `<wally-button variant="primary">Primary Button</wally-button>`,
|
|
20
|
+
secondaryVariant: `<wally-button variant="secondary">Secondary Button</wally-button>`,
|
|
21
|
+
|
|
18
22
|
// States
|
|
19
23
|
disabled: `<wally-button [disabled]="true">Disabled</wally-button>`,
|
|
20
24
|
loading: `<wally-button [loading]="true">Loading</wally-button>`,
|