@siemens/element-ng 49.10.0 → 49.12.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/carousel/package.json +4 -0
- package/fesm2022/siemens-element-ng-about.mjs +13 -4
- package/fesm2022/siemens-element-ng-about.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-ag-grid.mjs +6 -5
- package/fesm2022/siemens-element-ng-ag-grid.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-application-header.mjs +21 -24
- package/fesm2022/siemens-element-ng-application-header.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-breadcrumb-router.mjs +3 -3
- package/fesm2022/siemens-element-ng-breadcrumb-router.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-breadcrumb.mjs +3 -3
- package/fesm2022/siemens-element-ng-breadcrumb.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-card.mjs +2 -2
- package/fesm2022/siemens-element-ng-card.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-carousel.mjs +541 -0
- package/fesm2022/siemens-element-ng-carousel.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-chat-messages.mjs +18 -21
- package/fesm2022/siemens-element-ng-chat-messages.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-content-action-bar.mjs +2 -2
- package/fesm2022/siemens-element-ng-content-action-bar.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-dashboard.mjs +34 -35
- package/fesm2022/siemens-element-ng-dashboard.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-date-range-filter.mjs +20 -9
- package/fesm2022/siemens-element-ng-date-range-filter.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-datepicker.mjs +11 -10
- package/fesm2022/siemens-element-ng-datepicker.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-electron-titlebar.mjs +3 -3
- package/fesm2022/siemens-element-ng-electron-titlebar.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-empty-state.mjs +2 -2
- package/fesm2022/siemens-element-ng-empty-state.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-file-uploader.mjs +49 -47
- package/fesm2022/siemens-element-ng-file-uploader.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-filter-bar.mjs +5 -5
- package/fesm2022/siemens-element-ng-filter-bar.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-filtered-search.mjs +8 -7
- package/fesm2022/siemens-element-ng-filtered-search.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-footer.mjs +3 -3
- package/fesm2022/siemens-element-ng-footer.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-form.mjs +11 -10
- package/fesm2022/siemens-element-ng-form.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-formly.mjs +24 -23
- package/fesm2022/siemens-element-ng-formly.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-header-dropdown.mjs +26 -17
- package/fesm2022/siemens-element-ng-header-dropdown.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-help-button.mjs +3 -3
- package/fesm2022/siemens-element-ng-help-button.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-inline-notification.mjs +3 -3
- package/fesm2022/siemens-element-ng-inline-notification.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-landing-page.mjs +2 -2
- package/fesm2022/siemens-element-ng-landing-page.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-language-switcher.mjs +3 -3
- package/fesm2022/siemens-element-ng-language-switcher.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-markdown-renderer.mjs +4 -3
- package/fesm2022/siemens-element-ng-markdown-renderer.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-menu.mjs +2 -2
- package/fesm2022/siemens-element-ng-menu.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-navbar-vertical-next.mjs +362 -264
- package/fesm2022/siemens-element-ng-navbar-vertical-next.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-navbar-vertical.mjs +30 -13
- package/fesm2022/siemens-element-ng-navbar-vertical.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-navbar.mjs +6 -8
- package/fesm2022/siemens-element-ng-navbar.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-notification-item.mjs +3 -3
- package/fesm2022/siemens-element-ng-notification-item.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-pagination.mjs +4 -1
- package/fesm2022/siemens-element-ng-pagination.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-password-toggle.mjs +3 -3
- package/fesm2022/siemens-element-ng-password-toggle.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-photo-upload.mjs +2 -2
- package/fesm2022/siemens-element-ng-pills-input.mjs +4 -4
- package/fesm2022/siemens-element-ng-pills-input.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-popover-legacy.mjs +3 -3
- package/fesm2022/siemens-element-ng-popover-legacy.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-popover.mjs +3 -3
- package/fesm2022/siemens-element-ng-popover.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-result-details-list.mjs +3 -3
- package/fesm2022/siemens-element-ng-result-details-list.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-select.mjs +2 -2
- package/fesm2022/siemens-element-ng-side-panel.mjs +6 -6
- package/fesm2022/siemens-element-ng-side-panel.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-sort-bar.mjs +3 -3
- package/fesm2022/siemens-element-ng-sort-bar.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-split.mjs +3 -3
- package/fesm2022/siemens-element-ng-split.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-status-bar.mjs +3 -3
- package/fesm2022/siemens-element-ng-status-bar.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-threshold.mjs +5 -5
- package/fesm2022/siemens-element-ng-threshold.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-toast-notification.mjs +3 -3
- package/fesm2022/siemens-element-ng-toast-notification.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-tooltip.mjs +3 -3
- package/fesm2022/siemens-element-ng-tooltip.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-tour.mjs +2 -2
- package/fesm2022/siemens-element-ng-translate.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-tree-view.mjs +6 -6
- package/fesm2022/siemens-element-ng-tree-view.mjs.map +1 -1
- package/package.json +12 -8
- package/template-i18n.json +15 -6
- package/types/siemens-element-ng-about.d.ts +11 -1
- package/types/siemens-element-ng-application-header.d.ts +3 -4
- package/types/siemens-element-ng-carousel.d.ts +209 -0
- package/types/siemens-element-ng-chat-messages.d.ts +1 -2
- package/types/siemens-element-ng-dashboard.d.ts +4 -4
- package/types/siemens-element-ng-date-range-filter.d.ts +11 -2
- package/types/siemens-element-ng-file-uploader.d.ts +6 -8
- package/types/siemens-element-ng-header-dropdown.d.ts +3 -3
- package/types/siemens-element-ng-navbar-vertical-next.d.ts +161 -85
- package/types/siemens-element-ng-navbar.d.ts +1 -2
- package/types/siemens-element-ng-translate.d.ts +9 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, computed, ElementRef, signal, Directive, input, booleanAttribute, contentChildren, linkedSignal, DestroyRef, viewChild, ChangeDetectorRef, effect, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { elementPause, elementPlay, elementRight2, elementLeft2 } from '@siemens/element-icons';
|
|
5
|
+
import { addIcons, SiIconComponent } from '@siemens/element-ng/icon';
|
|
6
|
+
import { SiResizeObserverDirective } from '@siemens/element-ng/resize-observer';
|
|
7
|
+
import { t, injectSiTranslateService, SiTranslatePipe } from '@siemens/element-translate-ng/translate';
|
|
8
|
+
import { switchMap } from 'rxjs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Copyright (c) Siemens 2016 - 2026
|
|
12
|
+
* SPDX-License-Identifier: MIT
|
|
13
|
+
*/
|
|
14
|
+
class SiCarouselItemDirective {
|
|
15
|
+
carousel = inject(SiCarouselComponent);
|
|
16
|
+
isVisible = computed(() => {
|
|
17
|
+
if (this.carousel.currentActiveSlide() === this.elementRef) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
|
|
22
|
+
/** @internal */
|
|
23
|
+
elementRef = inject(ElementRef);
|
|
24
|
+
/** @internal */
|
|
25
|
+
order = signal(0, ...(ngDevMode ? [{ debugName: "order" }] : []));
|
|
26
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SiCarouselItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
27
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.8", type: SiCarouselItemDirective, isStandalone: true, selector: "[siCarouselItem]", host: { attributes: { "role": "group" }, properties: { "style.order": "order()", "attr.aria-roledescription": "carousel.translatedSlideRoleDescription()", "attr.inert": "isVisible() ? null : \"\"", "attr.aria-hidden": "isVisible() ? null : \"true\"" }, styleAttribute: "flex-shrink: 0; flex-basis: 100%; scroll-snap-align: center;" }, ngImport: i0 });
|
|
28
|
+
}
|
|
29
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SiCarouselItemDirective, decorators: [{
|
|
30
|
+
type: Directive,
|
|
31
|
+
args: [{
|
|
32
|
+
selector: '[siCarouselItem]',
|
|
33
|
+
host: {
|
|
34
|
+
style: 'flex-shrink: 0; flex-basis: 100%; scroll-snap-align: center;',
|
|
35
|
+
'[style.order]': 'order()',
|
|
36
|
+
'role': 'group',
|
|
37
|
+
'[attr.aria-roledescription]': 'carousel.translatedSlideRoleDescription()',
|
|
38
|
+
'[attr.inert]': 'isVisible() ? null : ""',
|
|
39
|
+
'[attr.aria-hidden]': 'isVisible() ? null : "true"'
|
|
40
|
+
}
|
|
41
|
+
}]
|
|
42
|
+
}] });
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Copyright (c) Siemens 2016 - 2026
|
|
46
|
+
* SPDX-License-Identifier: MIT
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* Maximum number of pagination dots shown in the pager.
|
|
50
|
+
*/
|
|
51
|
+
const MAX_PAGES = 5;
|
|
52
|
+
/**
|
|
53
|
+
* Time constant (in milliseconds) for the exponential-smoothing scroll animation. Smaller values
|
|
54
|
+
* settle faster. The animation is retargetable, so rapid clicks keep adjusting the same animation
|
|
55
|
+
* towards the latest slide instead of starting separate, queued scrolls.
|
|
56
|
+
*/
|
|
57
|
+
const SCROLL_SMOOTHING_TAU = 100;
|
|
58
|
+
/**
|
|
59
|
+
* Assumed duration (in milliseconds) of the first animation frame, used before two timestamps are
|
|
60
|
+
* available to measure a real delta. 16ms corresponds to one frame at ~60Hz, a safe default that
|
|
61
|
+
* keeps the first easing step the same size as a normal frame.
|
|
62
|
+
*/
|
|
63
|
+
const FIRST_FRAME_DELTA = 16;
|
|
64
|
+
/**
|
|
65
|
+
* Distance (in CSS pixels) below which the viewport is considered to have reached its target. Less
|
|
66
|
+
* than a pixel is imperceptible, so the animation snaps to the target and stops instead of easing
|
|
67
|
+
* across ever-smaller, invisible fractions of a pixel forever.
|
|
68
|
+
*/
|
|
69
|
+
const SCROLL_SETTLE_THRESHOLD = 0.5;
|
|
70
|
+
/**
|
|
71
|
+
* Per-frame progress (in CSS pixels) below which the animation is treated as stalled. The browser
|
|
72
|
+
* can cap `scrollLeft` a sub-pixel short of the computed target at the scroll boundary; without
|
|
73
|
+
* this guard the loop would never reach the target and would spin forever. Kept below
|
|
74
|
+
* {@link SCROLL_SETTLE_THRESHOLD} so it only triggers when real movement has effectively stopped.
|
|
75
|
+
*/
|
|
76
|
+
const SCROLL_STALL_THRESHOLD = 0.25;
|
|
77
|
+
/**
|
|
78
|
+
* A carousel component for displaying slides with navigation controls and auto-play.
|
|
79
|
+
*
|
|
80
|
+
* Features horizontal scrolling with snap behavior, pagination dots, and ARIA accessibility.
|
|
81
|
+
* Suitable for dashboards, image galleries, and content showcases.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
*
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { SiCarouselComponent, SiCarouselItemDirective } from '@siemens/element-ng/carousel';
|
|
87
|
+
*
|
|
88
|
+
* @Component({
|
|
89
|
+
* imports: [SiCarouselComponent, SiCarouselItemDirective],
|
|
90
|
+
* template: `
|
|
91
|
+
* <si-carousel>
|
|
92
|
+
* <div siCarouselItem><h2>Slide 1</h2></div>
|
|
93
|
+
* <div siCarouselItem><h2>Slide 2</h2></div>
|
|
94
|
+
* <div siCarouselItem><h2>Slide 3</h2></div>
|
|
95
|
+
* </si-carousel>
|
|
96
|
+
* `
|
|
97
|
+
* })
|
|
98
|
+
* export class MyComponent {}
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
class SiCarouselComponent {
|
|
102
|
+
/**
|
|
103
|
+
* Controls whether pagination controls are shown.
|
|
104
|
+
* When `true`, the carousel shows one slide per page and hides pagination controls.
|
|
105
|
+
* @defaultValue false
|
|
106
|
+
* */
|
|
107
|
+
hidePageControls = input(false, { ...(ngDevMode ? { debugName: "hidePageControls" } : {}), transform: booleanAttribute });
|
|
108
|
+
/**
|
|
109
|
+
* Enables or configures auto-play for the carousel. When set to `true`, the carousel automatically
|
|
110
|
+
* navigates to the next slide every 5000 milliseconds (5 seconds). Pass a positive number to set a
|
|
111
|
+
* custom interval in milliseconds. Pass `false` to disable auto-play.
|
|
112
|
+
* @defaultValue false
|
|
113
|
+
* */
|
|
114
|
+
autoPlay = input(false, ...(ngDevMode ? [{ debugName: "autoPlay" }] : []));
|
|
115
|
+
/**
|
|
116
|
+
* Aria role of the carousel element. Can be set to 'region' or 'group' depending on the desired accessibility semantics.
|
|
117
|
+
* @defaultValue 'region'
|
|
118
|
+
* */
|
|
119
|
+
ariaRole = input('region', ...(ngDevMode ? [{ debugName: "ariaRole" }] : []));
|
|
120
|
+
/**
|
|
121
|
+
* Aria label for the previous button, used for accessibility purposes.
|
|
122
|
+
* @defaultValue
|
|
123
|
+
* ```
|
|
124
|
+
* t(() => $localize`:@@SI_CAROUSEL.PREVIOUS:Previous slide`)
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
previousButtonAriaLabel = input(t(() => $localize `:@@SI_CAROUSEL.PREVIOUS:Previous slide`), ...(ngDevMode ? [{ debugName: "previousButtonAriaLabel" }] : []));
|
|
128
|
+
/**
|
|
129
|
+
* Aria label for the next button, used for accessibility purposes.
|
|
130
|
+
* @defaultValue
|
|
131
|
+
* ```
|
|
132
|
+
* t(() => $localize`:@@SI_CAROUSEL.NEXT:Next slide`)
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
nextButtonAriaLabel = input(t(() => $localize `:@@SI_CAROUSEL.NEXT:Next slide`), ...(ngDevMode ? [{ debugName: "nextButtonAriaLabel" }] : []));
|
|
136
|
+
/**
|
|
137
|
+
* @defaultValue
|
|
138
|
+
* ```
|
|
139
|
+
* t(() => $localize`:@@SI_CAROUSEL.SLIDE_PAGE:Slide {{page}} of {{total}}`)
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
slidePageAriaLabel = input(t(() => $localize `:@@SI_CAROUSEL.SLIDE_PAGE:Slide {{page}} of {{total}}`), ...(ngDevMode ? [{ debugName: "slidePageAriaLabel" }] : []));
|
|
143
|
+
/**
|
|
144
|
+
* Label for the play button when autoplay is paused.
|
|
145
|
+
* @defaultValue
|
|
146
|
+
* ```
|
|
147
|
+
* t(() => $localize`:@@SI_CAROUSEL.PLAY:Play`)
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
playButtonLabel = input(t(() => $localize `:@@SI_CAROUSEL.PLAY:Play`), ...(ngDevMode ? [{ debugName: "playButtonLabel" }] : []));
|
|
151
|
+
/**
|
|
152
|
+
* Label for the pause button when autoplay is active.
|
|
153
|
+
* @defaultValue
|
|
154
|
+
* ```
|
|
155
|
+
* t(() => $localize`:@@SI_CAROUSEL.PAUSE:Pause`)
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
pauseButtonLabel = input(t(() => $localize `:@@SI_CAROUSEL.PAUSE:Pause`), ...(ngDevMode ? [{ debugName: "pauseButtonLabel" }] : []));
|
|
159
|
+
/**
|
|
160
|
+
* Role description for the carousel container, used for accessibility purposes.
|
|
161
|
+
* @defaultValue
|
|
162
|
+
* ```
|
|
163
|
+
* t(() => $localize`:@@SI_CAROUSEL.ROLE_DESCRIPTION:carousel`)
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
carouselRoleDescription = input(t(() => $localize `:@@SI_CAROUSEL.ROLE_DESCRIPTION:carousel`), ...(ngDevMode ? [{ debugName: "carouselRoleDescription" }] : []));
|
|
167
|
+
/**
|
|
168
|
+
* Role description for each slide, used for accessibility purposes.
|
|
169
|
+
* @defaultValue
|
|
170
|
+
* ```
|
|
171
|
+
* t(() => $localize`:@@SI_CAROUSEL.SLIDE_ROLE_DESCRIPTION:Slide`)
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
slideRoleDescription = input(t(() => $localize `:@@SI_CAROUSEL.SLIDE_ROLE_DESCRIPTION:Slide`), ...(ngDevMode ? [{ debugName: "slideRoleDescription" }] : []));
|
|
175
|
+
/** @internal */
|
|
176
|
+
translatedSlideRoleDescription = toSignal(toObservable(this.slideRoleDescription).pipe(switchMap(key => this.translateService.translateAsync(key))), { initialValue: '' });
|
|
177
|
+
/** @internal */
|
|
178
|
+
currentActiveSlide = computed(() => {
|
|
179
|
+
const currentPage = this.currentPage();
|
|
180
|
+
if (currentPage !== null && this.items().length) {
|
|
181
|
+
return this.items()[currentPage].elementRef;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}, ...(ngDevMode ? [{ debugName: "currentActiveSlide" }] : []));
|
|
185
|
+
translatedCarouselRoleDescription = toSignal(toObservable(this.carouselRoleDescription).pipe(switchMap(key => this.translateService.translateAsync(key))), { initialValue: '' });
|
|
186
|
+
items = contentChildren(SiCarouselItemDirective, ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
187
|
+
currentPage = linkedSignal(() => this.items().length ? 0 : null, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
|
|
188
|
+
isPaused = signal(false, ...(ngDevMode ? [{ debugName: "isPaused" }] : []));
|
|
189
|
+
totalPages = computed(() => this.items().length, ...(ngDevMode ? [{ debugName: "totalPages" }] : []));
|
|
190
|
+
edgeLeftIndex = signal(0, ...(ngDevMode ? [{ debugName: "edgeLeftIndex" }] : []));
|
|
191
|
+
edgeRightIndex = signal(MAX_PAGES - 1, ...(ngDevMode ? [{ debugName: "edgeRightIndex" }] : []));
|
|
192
|
+
insufficientSpace = signal(false, ...(ngDevMode ? [{ debugName: "insufficientSpace" }] : []));
|
|
193
|
+
pageControlContainerMinWidth = signal(0, ...(ngDevMode ? [{ debugName: "pageControlContainerMinWidth" }] : []));
|
|
194
|
+
icons = addIcons({ elementLeft2, elementRight2, elementPlay, elementPause });
|
|
195
|
+
autoPlayDuration = computed(() => {
|
|
196
|
+
const autoPlayValue = this.autoPlay();
|
|
197
|
+
if (typeof autoPlayValue === 'boolean') {
|
|
198
|
+
return autoPlayValue ? 5000 : 0;
|
|
199
|
+
}
|
|
200
|
+
return autoPlayValue > 0 ? autoPlayValue : 0;
|
|
201
|
+
}, ...(ngDevMode ? [{ debugName: "autoPlayDuration" }] : []));
|
|
202
|
+
intervalId = null;
|
|
203
|
+
scrollObserver = null;
|
|
204
|
+
/** requestAnimationFrame id of the running scroll animation, or null when idle. */
|
|
205
|
+
animationFrameId = null;
|
|
206
|
+
/** Scroll position (`scrollLeft`) the running animation is easing towards. */
|
|
207
|
+
animationTargetLeft = 0;
|
|
208
|
+
/** Timestamp of the previous animation frame, used to make the easing frame-rate independent. */
|
|
209
|
+
lastFrameTime = 0;
|
|
210
|
+
/** Whether any slide's CSS order has been changed from its natural (source-index) position. */
|
|
211
|
+
ordersModified = false;
|
|
212
|
+
destroyRef = inject(DestroyRef);
|
|
213
|
+
viewport = viewChild.required('viewport');
|
|
214
|
+
pageControl = viewChild('pageControl', ...(ngDevMode ? [{ debugName: "pageControl" }] : []));
|
|
215
|
+
playPauseBtn = viewChild('playPauseBtn', ...(ngDevMode ? [{ debugName: "playPauseBtn" }] : []));
|
|
216
|
+
translateService = injectSiTranslateService();
|
|
217
|
+
cdRef = inject(ChangeDetectorRef);
|
|
218
|
+
constructor() {
|
|
219
|
+
effect(onCleanup => {
|
|
220
|
+
this.scheduleAutoPlay();
|
|
221
|
+
onCleanup(() => clearInterval(this.intervalId));
|
|
222
|
+
});
|
|
223
|
+
effect(() => {
|
|
224
|
+
// Reset to the first page if the current page becomes out of range due to items being added/removed.
|
|
225
|
+
const currentPage = this.currentPage();
|
|
226
|
+
if (currentPage !== null && currentPage >= this.totalPages()) {
|
|
227
|
+
this.cancelAnimation();
|
|
228
|
+
this.items().forEach((_, i) => this.setSlideOrder(i, i));
|
|
229
|
+
this.ordersModified = false;
|
|
230
|
+
this.goToPage(0);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
effect(() => {
|
|
234
|
+
this.setupScrollObserver();
|
|
235
|
+
});
|
|
236
|
+
effect(() => {
|
|
237
|
+
// Establish the canonical track order (each slide's CSS order equals its source index) so the
|
|
238
|
+
// navigation maths stay consistent. Skipped while an order change is mid-flight, where the
|
|
239
|
+
// orders are intentionally extended and normalized once the animation settles.
|
|
240
|
+
const items = this.items();
|
|
241
|
+
if (this.ordersModified) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
items.forEach((_, i) => this.setSlideOrder(i, i));
|
|
245
|
+
});
|
|
246
|
+
effect(() => {
|
|
247
|
+
this.updateEdgeIndices();
|
|
248
|
+
});
|
|
249
|
+
this.destroyRef.onDestroy(() => {
|
|
250
|
+
if (this.animationFrameId !== null) {
|
|
251
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
252
|
+
}
|
|
253
|
+
this.scrollObserver?.disconnect();
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
goToPage(page, forceScroll = true) {
|
|
257
|
+
this.currentPage.set(page);
|
|
258
|
+
this.scheduleAutoPlay();
|
|
259
|
+
if (forceScroll) {
|
|
260
|
+
this.animateToSlide(page);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
next() {
|
|
264
|
+
this.step(1);
|
|
265
|
+
}
|
|
266
|
+
previous() {
|
|
267
|
+
this.step(-1);
|
|
268
|
+
}
|
|
269
|
+
/** Navigates directly to a specific slide, used by the pagination dots. */
|
|
270
|
+
goToSlide(index) {
|
|
271
|
+
this.cancelAnimation();
|
|
272
|
+
this.normalizeOrders();
|
|
273
|
+
this.goToPage(index, true);
|
|
274
|
+
}
|
|
275
|
+
toggleAutoPlay() {
|
|
276
|
+
this.isPaused.update(paused => !paused);
|
|
277
|
+
if (this.isPaused()) {
|
|
278
|
+
this.stopAutoPlay();
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
this.scheduleAutoPlay();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
onResize(size) {
|
|
285
|
+
const pageControl = this.pageControl();
|
|
286
|
+
if (!pageControl) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const pageControlWidth = pageControl.nativeElement.offsetWidth;
|
|
290
|
+
if (this.autoPlay()) {
|
|
291
|
+
const playPauseBtnWidth = this.playPauseBtn()?.nativeElement.offsetWidth ?? 0;
|
|
292
|
+
const minWidth = pageControlWidth + playPauseBtnWidth;
|
|
293
|
+
if (minWidth > size.width - playPauseBtnWidth) {
|
|
294
|
+
this.insufficientSpace.set(true);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
this.insufficientSpace.set(false);
|
|
298
|
+
}
|
|
299
|
+
this.pageControlContainerMinWidth.set(minWidth);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
this.insufficientSpace.set(false);
|
|
303
|
+
this.pageControlContainerMinWidth.set(pageControlWidth);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Navigates a single slide forward (`1`) or backward (`-1`). Rapid clicks are intentionally not
|
|
308
|
+
* queued or throttled: each click advances {@link currentPage} immediately and retargets the
|
|
309
|
+
* running scroll animation (see {@link animateTo}) towards the new slide, so the carousel keeps
|
|
310
|
+
* gliding in the click direction without waiting for the previous scroll to finish.
|
|
311
|
+
*
|
|
312
|
+
* To keep the first/last wrap a continuous one-slide glide instead of a jump across the whole
|
|
313
|
+
* track, the target slide's CSS order is placed exactly one slot away from the current slide in
|
|
314
|
+
* the click direction (so it sits adjacent to it). The viewport scroll is compensated so this
|
|
315
|
+
* reorder is invisible. Orders may extend while the user keeps clicking; they are silently
|
|
316
|
+
* normalized once the animation settles (see {@link normalizeOrders}).
|
|
317
|
+
*/
|
|
318
|
+
step(direction) {
|
|
319
|
+
const total = this.totalPages();
|
|
320
|
+
if (total <= 1) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const from = this.currentPage() ?? 0;
|
|
324
|
+
const to = (from + direction + total) % total;
|
|
325
|
+
// Ensure the target slide sits one slot away from the current slide in the click direction.
|
|
326
|
+
// For a normal step it already does; for a first/last wrap it does not, so move it there.
|
|
327
|
+
const desiredOrder = this.items()[from].order() + direction;
|
|
328
|
+
if (this.items()[to].order() !== desiredOrder) {
|
|
329
|
+
this.reorderSlide(to, desiredOrder, from);
|
|
330
|
+
}
|
|
331
|
+
this.currentPage.set(to);
|
|
332
|
+
this.scheduleAutoPlay();
|
|
333
|
+
this.animateToSlide(to);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Changes a slide's CSS order while keeping the picture stable. Reordering shifts slides along
|
|
337
|
+
* the flex track, so the scroll position is adjusted by the exact amount the anchor slide moved,
|
|
338
|
+
* making the relocation invisible and letting any in-flight animation continue seamlessly.
|
|
339
|
+
*/
|
|
340
|
+
reorderSlide(index, order, anchorIndex) {
|
|
341
|
+
const viewport = this.viewport().nativeElement;
|
|
342
|
+
const anchor = this.items()[anchorIndex].elementRef.nativeElement;
|
|
343
|
+
const before = anchor.getBoundingClientRect().left;
|
|
344
|
+
this.setSlideOrder(index, order);
|
|
345
|
+
// Reading layout after the order change forces a synchronous reflow with the new positions.
|
|
346
|
+
const after = anchor.getBoundingClientRect().left;
|
|
347
|
+
viewport.scrollLeft += after - before;
|
|
348
|
+
this.ordersModified = true;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Sets the CSS order of a slide. The value is written to the DOM synchronously in addition to
|
|
352
|
+
* the signal, so a subsequent reflow/scroll uses the new order immediately instead of waiting
|
|
353
|
+
* for the asynchronous, signal-driven change detection update.
|
|
354
|
+
*/
|
|
355
|
+
setSlideOrder(index, order) {
|
|
356
|
+
const slide = this.items()[index];
|
|
357
|
+
slide.order.set(order);
|
|
358
|
+
slide.elementRef.nativeElement.style.order = `${order}`;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Silently restores the canonical slide order (each slide's CSS order equals its source index).
|
|
362
|
+
* Reordering shifts the track, so it anchors on the current slide and compensates the scroll
|
|
363
|
+
* position to avoid any visible jump. Called once the scroll animation settles.
|
|
364
|
+
*/
|
|
365
|
+
normalizeOrders() {
|
|
366
|
+
if (!this.ordersModified) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const items = this.items();
|
|
370
|
+
const viewport = this.viewport().nativeElement;
|
|
371
|
+
const current = this.currentPage();
|
|
372
|
+
const anchor = current !== null ? items[current]?.elementRef.nativeElement : undefined;
|
|
373
|
+
const before = anchor?.getBoundingClientRect().left ?? 0;
|
|
374
|
+
for (let i = 0; i < items.length; i++) {
|
|
375
|
+
if (items[i].order() !== i) {
|
|
376
|
+
this.setSlideOrder(i, i);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (anchor) {
|
|
380
|
+
const after = anchor.getBoundingClientRect().left;
|
|
381
|
+
viewport.scrollLeft += after - before;
|
|
382
|
+
}
|
|
383
|
+
this.ordersModified = false;
|
|
384
|
+
}
|
|
385
|
+
/** Animates the viewport so the given slide is horizontally centered. */
|
|
386
|
+
animateToSlide(index) {
|
|
387
|
+
const viewport = this.viewport().nativeElement;
|
|
388
|
+
const slide = this.items()[index].elementRef.nativeElement;
|
|
389
|
+
const viewportRect = viewport.getBoundingClientRect();
|
|
390
|
+
const slideRect = slide.getBoundingClientRect();
|
|
391
|
+
const delta = slideRect.left + slideRect.width / 2 - (viewportRect.left + viewportRect.width / 2);
|
|
392
|
+
this.animateTo(viewport.scrollLeft + delta);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Eases `scrollLeft` towards `targetLeft` using a frame-rate independent exponential smoothing
|
|
396
|
+
* loop. The target can be updated mid-flight (rapid clicks), so the same animation simply chases
|
|
397
|
+
* the latest slide. Native scroll-snap and smooth scroll-behavior are disabled while animating so
|
|
398
|
+
* they do not fight the manual scrolling, and the scroll observer is paused to avoid feedback.
|
|
399
|
+
*/
|
|
400
|
+
animateTo(targetLeft) {
|
|
401
|
+
this.animationTargetLeft = targetLeft;
|
|
402
|
+
if (this.animationFrameId !== null) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const viewport = this.viewport().nativeElement;
|
|
406
|
+
this.scrollObserver?.disconnect();
|
|
407
|
+
viewport.style.scrollSnapType = 'none';
|
|
408
|
+
viewport.style.scrollBehavior = 'auto';
|
|
409
|
+
this.lastFrameTime = 0;
|
|
410
|
+
this.animationFrameId = requestAnimationFrame(time => this.animationFrame(time));
|
|
411
|
+
}
|
|
412
|
+
animationFrame(now) {
|
|
413
|
+
const viewport = this.viewport().nativeElement;
|
|
414
|
+
const dt = this.lastFrameTime ? now - this.lastFrameTime : FIRST_FRAME_DELTA;
|
|
415
|
+
this.lastFrameTime = now;
|
|
416
|
+
const current = viewport.scrollLeft;
|
|
417
|
+
// Clamp the target to the scrollable range. A wrapped slide can be placed right at the track
|
|
418
|
+
// edge, where the rect-derived target may exceed the maximum by a sub-pixel; without clamping
|
|
419
|
+
// the loop would never reach it and never settle (leaving the order normalization undone).
|
|
420
|
+
const maxScrollLeft = viewport.scrollWidth - viewport.clientWidth;
|
|
421
|
+
const target = Math.max(0, Math.min(this.animationTargetLeft, maxScrollLeft));
|
|
422
|
+
const diff = target - current;
|
|
423
|
+
if (Math.abs(diff) < SCROLL_SETTLE_THRESHOLD) {
|
|
424
|
+
viewport.scrollLeft = target;
|
|
425
|
+
this.animationFrameId = null;
|
|
426
|
+
this.endAnimation();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
viewport.scrollLeft = current + diff * (1 - Math.exp(-dt / SCROLL_SMOOTHING_TAU));
|
|
430
|
+
// The browser can cap `scrollLeft` a sub-pixel short of the computed target (rounding at the
|
|
431
|
+
// scroll boundary). If a frame makes no real progress, treat the animation as settled instead
|
|
432
|
+
// of spinning forever.
|
|
433
|
+
if (Math.abs(viewport.scrollLeft - current) < SCROLL_STALL_THRESHOLD) {
|
|
434
|
+
this.animationFrameId = null;
|
|
435
|
+
this.endAnimation();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
this.animationFrameId = requestAnimationFrame(time => this.animationFrame(time));
|
|
439
|
+
}
|
|
440
|
+
/** Finalizes the animation: normalizes orders, restores native scrolling and the observer. */
|
|
441
|
+
endAnimation() {
|
|
442
|
+
this.normalizeOrders();
|
|
443
|
+
const viewport = this.viewport().nativeElement;
|
|
444
|
+
viewport.style.scrollSnapType = '';
|
|
445
|
+
viewport.style.scrollBehavior = '';
|
|
446
|
+
this.cdRef.markForCheck();
|
|
447
|
+
this.setupScrollObserver();
|
|
448
|
+
}
|
|
449
|
+
/** Stops the running scroll animation and restores native scrolling, without normalizing. */
|
|
450
|
+
cancelAnimation() {
|
|
451
|
+
if (this.animationFrameId !== null) {
|
|
452
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
453
|
+
this.animationFrameId = null;
|
|
454
|
+
}
|
|
455
|
+
const viewport = this.viewport().nativeElement;
|
|
456
|
+
viewport.style.scrollSnapType = '';
|
|
457
|
+
viewport.style.scrollBehavior = '';
|
|
458
|
+
this.cdRef.markForCheck();
|
|
459
|
+
}
|
|
460
|
+
scheduleAutoPlay() {
|
|
461
|
+
if (this.intervalId) {
|
|
462
|
+
clearInterval(this.intervalId);
|
|
463
|
+
}
|
|
464
|
+
if (!this.autoPlayDuration() || this.items().length === 0 || this.isPaused()) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
this.intervalId = setInterval(() => this.next(), this.autoPlayDuration());
|
|
468
|
+
}
|
|
469
|
+
stopAutoPlay() {
|
|
470
|
+
if (this.intervalId) {
|
|
471
|
+
clearInterval(this.intervalId);
|
|
472
|
+
this.intervalId = null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
setupScrollObserver() {
|
|
476
|
+
if (this.scrollObserver) {
|
|
477
|
+
this.scrollObserver.disconnect();
|
|
478
|
+
}
|
|
479
|
+
const items = this.items();
|
|
480
|
+
if (items.length === 0) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const viewport = this.viewport().nativeElement;
|
|
484
|
+
this.scrollObserver = new IntersectionObserver((entries) => {
|
|
485
|
+
entries.forEach(entry => {
|
|
486
|
+
if (entry.isIntersecting) {
|
|
487
|
+
const target = entry.target;
|
|
488
|
+
const itemIndex = items.findIndex(item => item.elementRef.nativeElement === target);
|
|
489
|
+
if (itemIndex !== -1) {
|
|
490
|
+
this.goToPage(itemIndex, false);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}, { root: viewport, threshold: 0.6, rootMargin: '0px' });
|
|
495
|
+
for (const item of items) {
|
|
496
|
+
this.scrollObserver.observe(item.elementRef.nativeElement);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
updateEdgeIndices() {
|
|
500
|
+
const total = this.totalPages();
|
|
501
|
+
const current = this.currentPage();
|
|
502
|
+
if (current === null) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (total <= MAX_PAGES) {
|
|
506
|
+
this.edgeLeftIndex.set(0);
|
|
507
|
+
this.edgeRightIndex.set(total - 1);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (current < this.edgeLeftIndex()) {
|
|
511
|
+
this.edgeLeftIndex.set(current);
|
|
512
|
+
this.edgeRightIndex.set(current + MAX_PAGES - 1);
|
|
513
|
+
}
|
|
514
|
+
if (current > this.edgeRightIndex()) {
|
|
515
|
+
this.edgeRightIndex.set(current);
|
|
516
|
+
this.edgeLeftIndex.set(current - MAX_PAGES + 1);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SiCarouselComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
520
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: SiCarouselComponent, isStandalone: true, selector: "si-carousel", inputs: { hidePageControls: { classPropertyName: "hidePageControls", publicName: "hidePageControls", isSignal: true, isRequired: false, transformFunction: null }, autoPlay: { classPropertyName: "autoPlay", publicName: "autoPlay", isSignal: true, isRequired: false, transformFunction: null }, ariaRole: { classPropertyName: "ariaRole", publicName: "ariaRole", isSignal: true, isRequired: false, transformFunction: null }, previousButtonAriaLabel: { classPropertyName: "previousButtonAriaLabel", publicName: "previousButtonAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, nextButtonAriaLabel: { classPropertyName: "nextButtonAriaLabel", publicName: "nextButtonAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, slidePageAriaLabel: { classPropertyName: "slidePageAriaLabel", publicName: "slidePageAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, playButtonLabel: { classPropertyName: "playButtonLabel", publicName: "playButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, pauseButtonLabel: { classPropertyName: "pauseButtonLabel", publicName: "pauseButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, carouselRoleDescription: { classPropertyName: "carouselRoleDescription", publicName: "carouselRoleDescription", isSignal: true, isRequired: false, transformFunction: null }, slideRoleDescription: { classPropertyName: "slideRoleDescription", publicName: "slideRoleDescription", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "ariaRole()", "attr.aria-roledescription": "translatedCarouselRoleDescription()", "style.min-width.px": "pageControlContainerMinWidth()" } }, queries: [{ propertyName: "items", predicate: SiCarouselItemDirective, isSignal: true }], viewQueries: [{ propertyName: "viewport", first: true, predicate: ["viewport"], descendants: true, isSignal: true }, { propertyName: "pageControl", first: true, predicate: ["pageControl"], descendants: true, isSignal: true }, { propertyName: "playPauseBtn", first: true, predicate: ["playPauseBtn"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"carousel-body d-flex flex-grow-1 align-items-center gap-2 min-h-0\">\n <div #viewport class=\"carousel-viewport flex-grow-1 align-self-stretch\" tabindex=\"0\">\n <div class=\"carousel-track h-100\">\n <ng-content />\n </div>\n </div>\n</div>\n@if ((!hidePageControls() || autoPlay()) && totalPages() > 1) {\n <div\n class=\"position-relative d-flex justify-content-center align-items-center mt-4\"\n (siResizeObserver)=\"onResize($event)\"\n >\n @if (!hidePageControls()) {\n <div #pageControl class=\"d-flex align-items-center gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-icon btn-tertiary-ghost flex-shrink-0\"\n [aria-label]=\"previousButtonAriaLabel() | translate\"\n (click)=\"previous()\"\n >\n <si-icon [icon]=\"icons.elementLeft2\"\n /></button>\n <div class=\"d-flex align-items-center\">\n <div\n #dotBtn\n class=\"btn btn-tertiary-ghost btn-icon btn-sm pe-none\"\n [class.opacity-0]=\"totalPages() <= 5\"\n [class.position-absolute]=\"totalPages() <= 5\"\n >\n <span class=\"carousel-pager-small-dot d-block rounded-circle\"></span>\n </div>\n <div\n class=\"d-flex overflow-hidden\"\n [style.width.px]=\"dotBtn.clientWidth * (totalPages() > 5 ? 5 : totalPages())\"\n >\n <div\n class=\"d-flex page-buttons\"\n [style.transform]=\"'translateX(-' + edgeLeftIndex() * dotBtn.clientWidth + 'px)'\"\n >\n @for (page of items(); track page) {\n <button\n type=\"button\"\n class=\"btn btn-tertiary-ghost focus-inside btn-icon btn-sm d-flex align-items-center justify-content-center\"\n [aria-label]=\"\n slidePageAriaLabel() | translate: { page: $index + 1, total: totalPages() }\n \"\n [tabIndex]=\"$index >= edgeLeftIndex() && $index <= edgeRightIndex() ? 0 : -1\"\n [aria-current]=\"currentPage() === $index ? 'true' : 'false'\"\n (click)=\"goToSlide($index)\"\n ><span\n class=\"carousel-pager-dot d-block\"\n [class.active]=\"currentPage() === $index\"\n [class.rounded-circle]=\"currentPage() !== $index\"\n ></span\n ></button>\n }\n </div>\n </div>\n <div\n #dotBtn\n class=\"btn btn-tertiary-ghost btn-icon btn-sm pe-none\"\n [class.opacity-0]=\"totalPages() <= 5\"\n [class.position-absolute]=\"totalPages() <= 5\"\n >\n <span class=\"carousel-pager-small-dot d-block rounded-circle\"></span>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"btn btn-icon btn-tertiary-ghost flex-shrink-0\"\n [aria-label]=\"nextButtonAriaLabel() | translate\"\n (click)=\"next()\"\n >\n <si-icon [icon]=\"icons.elementRight2\"\n /></button>\n </div>\n }\n\n @if (autoPlay()) {\n <div\n #playPauseBtn\n class=\"position-absolute top-0 end-0\"\n [class.invisible]=\"insufficientSpace()\"\n >\n <button\n type=\"button\"\n class=\"btn btn-tertiary-ghost ms-2\"\n [aria-label]=\"(isPaused() ? playButtonLabel() : pauseButtonLabel()) | translate\"\n (click)=\"toggleAutoPlay()\"\n >\n <si-icon class=\"icon\" [icon]=\"isPaused() ? icons.elementPlay : icons.elementPause\" />\n <span aria-hidden=\"true\">{{\n (isPaused() ? playButtonLabel() : pauseButtonLabel()) | translate\n }}</span>\n </button>\n </div>\n @if (insufficientSpace()) {\n <button\n type=\"button\"\n class=\"btn btn-icon btn-tertiary-ghost flex-shrink-0 align-items-center position-absolute top-0 end-0 ms-2\"\n [aria-label]=\"(isPaused() ? playButtonLabel() : pauseButtonLabel()) | translate\"\n (click)=\"toggleAutoPlay()\"\n >\n <si-icon [icon]=\"isPaused() ? icons.elementPlay : icons.elementPause\" />\n </button>\n }\n }\n </div>\n}\n", styles: [":host{--si-carousel-gap: 16px;display:flex;flex-direction:column;flex:1 1 0}.carousel-viewport{min-block-size:0;overflow-y:hidden;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none}.page-buttons{transition:transform calc(.5s * var(--element-animations-enabled, 1)) ease}.carousel-track{display:flex;gap:var(--si-carousel-gap)}.carousel-pager-dot{inline-size:.375rem;block-size:.375rem;background-color:var(--element-ui-3);transition:background-color calc(.5s * var(--element-animations-enabled, 1)) ease}.carousel-pager-dot.active{transition:inline-size calc(.5s * var(--element-animations-enabled, 1)) ease,background-color calc(.5s * var(--element-animations-enabled, 1)) ease;inline-size:.75rem;background-color:var(--element-ui-0);border-radius:6.25rem}.carousel-pager-small-dot{background-color:var(--element-ui-3);inline-size:.25rem;block-size:.25rem}\n"], dependencies: [{ kind: "directive", type: SiResizeObserverDirective, selector: "[siResizeObserver]", inputs: ["resizeThrottle", "emitInitial"], outputs: ["siResizeObserver"] }, { kind: "component", type: SiIconComponent, selector: "si-icon", inputs: ["icon"] }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
521
|
+
}
|
|
522
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SiCarouselComponent, decorators: [{
|
|
523
|
+
type: Component,
|
|
524
|
+
args: [{ selector: 'si-carousel', imports: [SiTranslatePipe, SiResizeObserverDirective, SiIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
525
|
+
'[attr.role]': 'ariaRole()',
|
|
526
|
+
'[attr.aria-roledescription]': 'translatedCarouselRoleDescription()',
|
|
527
|
+
'[style.min-width.px]': 'pageControlContainerMinWidth()'
|
|
528
|
+
}, template: "<div class=\"carousel-body d-flex flex-grow-1 align-items-center gap-2 min-h-0\">\n <div #viewport class=\"carousel-viewport flex-grow-1 align-self-stretch\" tabindex=\"0\">\n <div class=\"carousel-track h-100\">\n <ng-content />\n </div>\n </div>\n</div>\n@if ((!hidePageControls() || autoPlay()) && totalPages() > 1) {\n <div\n class=\"position-relative d-flex justify-content-center align-items-center mt-4\"\n (siResizeObserver)=\"onResize($event)\"\n >\n @if (!hidePageControls()) {\n <div #pageControl class=\"d-flex align-items-center gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-icon btn-tertiary-ghost flex-shrink-0\"\n [aria-label]=\"previousButtonAriaLabel() | translate\"\n (click)=\"previous()\"\n >\n <si-icon [icon]=\"icons.elementLeft2\"\n /></button>\n <div class=\"d-flex align-items-center\">\n <div\n #dotBtn\n class=\"btn btn-tertiary-ghost btn-icon btn-sm pe-none\"\n [class.opacity-0]=\"totalPages() <= 5\"\n [class.position-absolute]=\"totalPages() <= 5\"\n >\n <span class=\"carousel-pager-small-dot d-block rounded-circle\"></span>\n </div>\n <div\n class=\"d-flex overflow-hidden\"\n [style.width.px]=\"dotBtn.clientWidth * (totalPages() > 5 ? 5 : totalPages())\"\n >\n <div\n class=\"d-flex page-buttons\"\n [style.transform]=\"'translateX(-' + edgeLeftIndex() * dotBtn.clientWidth + 'px)'\"\n >\n @for (page of items(); track page) {\n <button\n type=\"button\"\n class=\"btn btn-tertiary-ghost focus-inside btn-icon btn-sm d-flex align-items-center justify-content-center\"\n [aria-label]=\"\n slidePageAriaLabel() | translate: { page: $index + 1, total: totalPages() }\n \"\n [tabIndex]=\"$index >= edgeLeftIndex() && $index <= edgeRightIndex() ? 0 : -1\"\n [aria-current]=\"currentPage() === $index ? 'true' : 'false'\"\n (click)=\"goToSlide($index)\"\n ><span\n class=\"carousel-pager-dot d-block\"\n [class.active]=\"currentPage() === $index\"\n [class.rounded-circle]=\"currentPage() !== $index\"\n ></span\n ></button>\n }\n </div>\n </div>\n <div\n #dotBtn\n class=\"btn btn-tertiary-ghost btn-icon btn-sm pe-none\"\n [class.opacity-0]=\"totalPages() <= 5\"\n [class.position-absolute]=\"totalPages() <= 5\"\n >\n <span class=\"carousel-pager-small-dot d-block rounded-circle\"></span>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"btn btn-icon btn-tertiary-ghost flex-shrink-0\"\n [aria-label]=\"nextButtonAriaLabel() | translate\"\n (click)=\"next()\"\n >\n <si-icon [icon]=\"icons.elementRight2\"\n /></button>\n </div>\n }\n\n @if (autoPlay()) {\n <div\n #playPauseBtn\n class=\"position-absolute top-0 end-0\"\n [class.invisible]=\"insufficientSpace()\"\n >\n <button\n type=\"button\"\n class=\"btn btn-tertiary-ghost ms-2\"\n [aria-label]=\"(isPaused() ? playButtonLabel() : pauseButtonLabel()) | translate\"\n (click)=\"toggleAutoPlay()\"\n >\n <si-icon class=\"icon\" [icon]=\"isPaused() ? icons.elementPlay : icons.elementPause\" />\n <span aria-hidden=\"true\">{{\n (isPaused() ? playButtonLabel() : pauseButtonLabel()) | translate\n }}</span>\n </button>\n </div>\n @if (insufficientSpace()) {\n <button\n type=\"button\"\n class=\"btn btn-icon btn-tertiary-ghost flex-shrink-0 align-items-center position-absolute top-0 end-0 ms-2\"\n [aria-label]=\"(isPaused() ? playButtonLabel() : pauseButtonLabel()) | translate\"\n (click)=\"toggleAutoPlay()\"\n >\n <si-icon [icon]=\"isPaused() ? icons.elementPlay : icons.elementPause\" />\n </button>\n }\n }\n </div>\n}\n", styles: [":host{--si-carousel-gap: 16px;display:flex;flex-direction:column;flex:1 1 0}.carousel-viewport{min-block-size:0;overflow-y:hidden;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none}.page-buttons{transition:transform calc(.5s * var(--element-animations-enabled, 1)) ease}.carousel-track{display:flex;gap:var(--si-carousel-gap)}.carousel-pager-dot{inline-size:.375rem;block-size:.375rem;background-color:var(--element-ui-3);transition:background-color calc(.5s * var(--element-animations-enabled, 1)) ease}.carousel-pager-dot.active{transition:inline-size calc(.5s * var(--element-animations-enabled, 1)) ease,background-color calc(.5s * var(--element-animations-enabled, 1)) ease;inline-size:.75rem;background-color:var(--element-ui-0);border-radius:6.25rem}.carousel-pager-small-dot{background-color:var(--element-ui-3);inline-size:.25rem;block-size:.25rem}\n"] }]
|
|
529
|
+
}], ctorParameters: () => [], propDecorators: { hidePageControls: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidePageControls", required: false }] }], autoPlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoPlay", required: false }] }], ariaRole: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaRole", required: false }] }], previousButtonAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousButtonAriaLabel", required: false }] }], nextButtonAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextButtonAriaLabel", required: false }] }], slidePageAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "slidePageAriaLabel", required: false }] }], playButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "playButtonLabel", required: false }] }], pauseButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pauseButtonLabel", required: false }] }], carouselRoleDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "carouselRoleDescription", required: false }] }], slideRoleDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideRoleDescription", required: false }] }], items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SiCarouselItemDirective), { isSignal: true }] }], viewport: [{ type: i0.ViewChild, args: ['viewport', { isSignal: true }] }], pageControl: [{ type: i0.ViewChild, args: ['pageControl', { isSignal: true }] }], playPauseBtn: [{ type: i0.ViewChild, args: ['playPauseBtn', { isSignal: true }] }] } });
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Copyright (c) Siemens 2016 - 2026
|
|
533
|
+
* SPDX-License-Identifier: MIT
|
|
534
|
+
*/
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Generated bundle index. Do not edit.
|
|
538
|
+
*/
|
|
539
|
+
|
|
540
|
+
export { SiCarouselComponent, SiCarouselItemDirective };
|
|
541
|
+
//# sourceMappingURL=siemens-element-ng-carousel.mjs.map
|