@eui/ecl 22.0.0-alpha.51 → 22.0.0-alpha.53
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/assets/i18n-ecl/bg.json +6 -2
- package/assets/i18n-ecl/cs.json +6 -2
- package/assets/i18n-ecl/da.json +6 -2
- package/assets/i18n-ecl/de.json +6 -2
- package/assets/i18n-ecl/el.json +6 -2
- package/assets/i18n-ecl/en.json +6 -2
- package/assets/i18n-ecl/es.json +6 -2
- package/assets/i18n-ecl/et.json +6 -2
- package/assets/i18n-ecl/fi.json +6 -2
- package/assets/i18n-ecl/fr.json +6 -2
- package/assets/i18n-ecl/ga.json +6 -2
- package/assets/i18n-ecl/hr.json +6 -2
- package/assets/i18n-ecl/hu.json +6 -2
- package/assets/i18n-ecl/it.json +6 -2
- package/assets/i18n-ecl/lt.json +6 -2
- package/assets/i18n-ecl/lv.json +6 -2
- package/assets/i18n-ecl/mt.json +6 -2
- package/assets/i18n-ecl/nl.json +6 -2
- package/assets/i18n-ecl/pl.json +6 -2
- package/assets/i18n-ecl/pt.json +6 -2
- package/assets/i18n-ecl/ro.json +6 -2
- package/assets/i18n-ecl/sk.json +6 -2
- package/assets/i18n-ecl/sl.json +6 -2
- package/assets/i18n-ecl/sv.json +6 -2
- package/components/ecl-slider/package.json +4 -0
- package/docs/changelog.html +29 -0
- package/docs/components/EclAccordionSummaryComponent.html +1 -1
- package/docs/components/EclAddToCalendarComponent.html +1 -1
- package/docs/components/EclAnimatedNumbersComponent.html +1 -1
- package/docs/components/EclAnimatedNumbersItemComponent.html +1 -1
- package/docs/components/EclAppComponent.html +1 -1
- package/docs/components/EclBannerComponent.html +1 -1
- package/docs/components/EclBlockquoteComponent.html +1 -1
- package/docs/components/EclBreadcrumbComponent.html +1 -1
- package/docs/components/EclBreadcrumbSegmentComponent.html +1 -1
- package/docs/components/EclButtonComponent.html +1 -1
- package/docs/components/EclCardBodyComponent.html +1 -1
- package/docs/components/EclCardComponent.html +1 -1
- package/docs/components/EclCarouselComponent.html +182 -898
- package/docs/components/EclCarouselItemComponent.html +32 -1
- package/docs/components/EclCategoryFilterComponent.html +1 -1
- package/docs/components/EclCategoryFilterItemComponent.html +1 -1
- package/docs/components/EclCategoryFilterListComponent.html +1 -1
- package/docs/components/EclCheckboxHelpComponent.html +1 -1
- package/docs/components/EclCheckboxLabelComponent.html +1 -1
- package/docs/components/EclContentBlockComponent.html +1 -1
- package/docs/components/EclContentItemComponent.html +1 -1
- package/docs/components/EclDateBlockComponent.html +1 -1
- package/docs/components/EclDescriptionListDefinitionComponent.html +1 -1
- package/docs/components/EclExpandableComponent.html +1 -1
- package/docs/components/EclFactFiguresComponent.html +1 -1
- package/docs/components/EclFactFiguresItemComponent.html +1 -1
- package/docs/components/EclFactFiguresViewAllComponent.html +1 -1
- package/docs/components/EclFeaturedComponent.html +1 -1
- package/docs/components/EclFeaturedItemComponent.html +1 -1
- package/docs/components/EclFeaturedItemDescriptionComponent.html +1 -1
- package/docs/components/EclFileComponent.html +1 -1
- package/docs/components/EclFileItemComponent.html +1 -1
- package/docs/components/EclFileItemsComponent.html +1 -1
- package/docs/components/EclFileListsComponent.html +1 -1
- package/docs/components/EclFormGroupComponent.html +1 -1
- package/docs/components/EclFormLabelComponent.html +1 -1
- package/docs/components/EclGalleryComponent.html +1 -1
- package/docs/components/EclGalleryFooterComponent.html +1 -1
- package/docs/components/EclGalleryItemComponent.html +1 -1
- package/docs/components/EclHighlightBoxComponent.html +1 -1
- package/docs/components/EclHighlightedSearchComponent.html +1 -1
- package/docs/components/EclIconComponent.html +1 -10
- package/docs/components/EclIndicatorComponent.html +1 -1
- package/docs/components/EclInpageNavigationComponent.html +1 -1
- package/docs/components/EclInpageNavigationItemComponent.html +1 -1
- package/docs/components/EclLayoutWrapperComponent.html +1 -1
- package/docs/components/EclListIllustrationComponent.html +1 -1
- package/docs/components/EclListIllustrationItemComponent.html +1 -1
- package/docs/components/EclLoadingIndicatorComponent.html +1 -1
- package/docs/components/EclMediaContainerComponent.html +1 -1
- package/docs/components/EclMegaMenuComponent.html +1 -1
- package/docs/components/EclMegaMenuFeaturedComponent.html +1 -1
- package/docs/components/EclMegaMenuInfoComponent.html +1 -1
- package/docs/components/EclMegaMenuItemComponent.html +1 -1
- package/docs/components/EclMegaMenuSubitemComponent.html +1 -1
- package/docs/components/EclMenuComponent.html +1 -1
- package/docs/components/EclMenuItemComponent.html +1 -1
- package/docs/components/EclMenuMegaComponent.html +1 -1
- package/docs/components/EclMenuMegaItemComponent.html +1 -1
- package/docs/components/EclModalBodyComponent.html +1 -1
- package/docs/components/EclModalComponent.html +1 -1
- package/docs/components/EclModalFooterComponent.html +1 -1
- package/docs/components/EclModalHeaderComponent.html +1 -1
- package/docs/components/EclMultiselectComponent.html +1 -1
- package/docs/components/EclMultiselectDropdownComponent.html +1 -1
- package/docs/components/EclMultiselectInputComponent.html +1 -1
- package/docs/components/EclMultiselectOptgroupComponent.html +1 -1
- package/docs/components/EclMultiselectOptionComponent.html +1 -1
- package/docs/components/EclNavigationListComponent.html +1 -1
- package/docs/components/EclNavigationListItemComponent.html +1 -1
- package/docs/components/EclNewsTickerComponent.html +1 -1
- package/docs/components/EclNewsTickerItemComponent.html +1 -1
- package/docs/components/EclNotificationComponent.html +1 -1
- package/docs/components/EclPageHeaderComponent.html +1 -1
- package/docs/components/EclPageHeaderDescriptionContainerComponent.html +1 -1
- package/docs/components/EclPageHeaderExpandableComponent.html +1 -1
- package/docs/components/EclPageHeaderSectionComponent.html +1 -1
- package/docs/components/EclPageHeaderSectionInfoComponent.html +1 -1
- package/docs/components/EclPageSummaryComponent.html +1 -1
- package/docs/components/EclPaginationComponent.html +1 -1
- package/docs/components/EclPaginationItemComponent.html +1 -1
- package/docs/components/EclPopoverComponent.html +1 -1
- package/docs/components/EclQuizCardComponent.html +113 -44
- package/docs/components/EclQuizComponent.html +4 -9
- package/docs/components/EclRadioLabelComponent.html +1 -1
- package/docs/components/EclRangeBubbleComponent.html +1 -1
- package/docs/components/EclRangeValueComponent.html +1 -1
- package/docs/components/EclRatingFieldComponent.html +1 -1
- package/docs/components/EclSearchFormComponent.html +1 -1
- package/docs/components/EclSelectContainerComponent.html +1 -1
- package/docs/components/EclSiteFooterComponent.html +1 -1
- package/docs/components/EclSiteFooterCoownerComponent.html +1 -1
- package/docs/components/EclSiteFooterCoreComponent.html +1 -1
- package/docs/components/EclSiteFooterFeedbackComponent.html +1 -1
- package/docs/components/EclSiteFooterFixedContentEUComponent.html +1 -1
- package/docs/components/EclSiteFooterRowCommonComponent.html +1 -1
- package/docs/components/EclSiteHeaderActionComponent.html +1 -1
- package/docs/components/EclSiteHeaderBannerTopComponent.html +1 -1
- package/docs/components/EclSiteHeaderComponent.html +1 -1
- package/docs/components/EclSiteHeaderCustomActionComponent.html +1 -1
- package/docs/components/EclSiteHeaderEnvironmentComponent.html +1 -1
- package/docs/components/EclSiteHeaderLanguageComponent.html +1 -1
- package/docs/components/EclSiteHeaderLanguagePopoverComponent.html +1 -1
- package/docs/components/EclSiteHeaderLoginComponent.html +1 -1
- package/docs/components/EclSiteHeaderNotificationComponent.html +1 -1
- package/docs/components/EclSiteHeaderSearchComponent.html +1 -1
- package/docs/components/EclSliderPagerComponent.html +1159 -0
- package/docs/components/EclSloganTickerComponent.html +1 -1
- package/docs/components/EclSocialMediaFollowComponent.html +1 -1
- package/docs/components/EclSocialMediaFollowItemComponent.html +1 -1
- package/docs/components/EclSplashPageComponent.html +1 -1
- package/docs/components/EclSplashPageLanguageCategoryComponent.html +1 -1
- package/docs/components/EclSplashPageLanguageContainerComponent.html +1 -1
- package/docs/components/EclSplashPageLanguageLinkComponent.html +1 -1
- package/docs/components/EclSpotlightComponent.html +1 -1
- package/docs/components/EclSpotlightLinkComponent.html +1 -1
- package/docs/components/EclStickyContainerComponent.html +1 -1
- package/docs/components/EclStoryCardComponent.html +4 -4
- package/docs/components/EclStoryCardGridDetailsComponent.html +57 -2
- package/docs/components/EclStoryCardItemComponent.html +77 -2
- package/docs/components/EclTabComponent.html +1 -1
- package/docs/components/EclTabLabelComponent.html +1 -1
- package/docs/components/EclTableSortButtonComponent.html +1 -1
- package/docs/components/EclTabsComponent.html +1 -1
- package/docs/components/EclTagComponent.html +1 -1
- package/docs/components/EclTextMediaComponent.html +1 -1
- package/docs/components/EclTimelineComponent.html +1 -1
- package/docs/components/EclTimelineItemComponent.html +1 -1
- package/docs/components/EclTimelineItemTogglerComponent.html +1 -1
- package/docs/js/menu-wc.js +3 -0
- package/docs/js/search/search_index.js +2 -2
- package/docs/json/EclCarouselComponent.md +8 -4
- package/docs/json/EclCarouselItemComponent.md +1 -0
- package/docs/json/EclQuizCardComponent.md +6 -3
- package/docs/json/EclSliderPagerComponent.md +55 -0
- package/docs/json/EclStoryCardGridDetailsComponent.md +1 -0
- package/docs/json/EclStoryCardItemComponent.md +1 -0
- package/docs/json/documentation.json +1889 -565
- package/docs/llms.txt +67 -32
- package/docs/miscellaneous/variables.html +112 -0
- package/docs/overview.html +1 -1
- package/docs/properties.html +1 -1
- package/fesm2022/eui-ecl-components-ecl-carousel.mjs +405 -407
- package/fesm2022/eui-ecl-components-ecl-carousel.mjs.map +1 -1
- package/fesm2022/eui-ecl-components-ecl-menu.mjs +15 -15
- package/fesm2022/eui-ecl-components-ecl-menu.mjs.map +1 -1
- package/fesm2022/eui-ecl-components-ecl-quiz.mjs +93 -55
- package/fesm2022/eui-ecl-components-ecl-quiz.mjs.map +1 -1
- package/fesm2022/eui-ecl-components-ecl-slider.mjs +187 -0
- package/fesm2022/eui-ecl-components-ecl-slider.mjs.map +1 -0
- package/fesm2022/eui-ecl-components-ecl-story-card.mjs +96 -18
- package/fesm2022/eui-ecl-components-ecl-story-card.mjs.map +1 -1
- package/package.json +18 -12
- package/types/eui-ecl-components-ecl-carousel.d.ts +234 -202
- package/types/eui-ecl-components-ecl-carousel.d.ts.map +1 -1
- package/types/eui-ecl-components-ecl-menu.d.ts +0 -2
- package/types/eui-ecl-components-ecl-menu.d.ts.map +1 -1
- package/types/eui-ecl-components-ecl-quiz.d.ts +94 -57
- package/types/eui-ecl-components-ecl-quiz.d.ts.map +1 -1
- package/types/eui-ecl-components-ecl-slider.d.ts +118 -0
- package/types/eui-ecl-components-ecl-slider.d.ts.map +1 -0
- package/types/eui-ecl-components-ecl-story-card.d.ts +78 -48
- package/types/eui-ecl-components-ecl-story-card.d.ts.map +1 -1
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
3
|
import { inject, ElementRef, signal, input, booleanAttribute, linkedSignal, output, contentChild, forwardRef, computed, Component, numberAttribute, viewChild, contentChildren, viewChildren, ChangeDetectorRef, Renderer2, DOCUMENT, afterNextRender } from '@angular/core';
|
|
4
|
-
import
|
|
4
|
+
import * as i1 from '@eui/ecl/components/ecl-slider';
|
|
5
|
+
import { EUI_ECL_SLIDER } from '@eui/ecl/components/ecl-slider';
|
|
5
6
|
import { EclBaseEvent, ECLBaseDirective, EclRtlService, KeyCode } from '@eui/ecl/core';
|
|
6
|
-
import { TranslatePipe } from '@ngx-translate/core';
|
|
7
|
-
import {
|
|
7
|
+
import { TranslateService, TranslatePipe } from '@ngx-translate/core';
|
|
8
|
+
import { EmblaCarouselDirective } from 'embla-carousel-angular';
|
|
9
|
+
import Autoplay from 'embla-carousel-autoplay';
|
|
10
|
+
import { Subject, fromEvent, auditTime, takeUntil } from 'rxjs';
|
|
8
11
|
import { EclBannerComponent } from '@eui/ecl/components/ecl-banner';
|
|
9
12
|
|
|
10
13
|
/**
|
|
@@ -61,6 +64,12 @@ class EclCarouselItemComponent extends ECLBaseDirective {
|
|
|
61
64
|
*/
|
|
62
65
|
this.isCurrent = linkedSignal(() => this.isCurrentInput(), /* @ts-ignore */
|
|
63
66
|
...(ngDevMode ? [{ debugName: "isCurrent" }] : /* istanbul ignore next */ []));
|
|
67
|
+
/**
|
|
68
|
+
* Text shown in the carousel teaser (navigation) button for this slide.
|
|
69
|
+
* Optional; when empty, no teaser label is rendered for the slide.
|
|
70
|
+
*/
|
|
71
|
+
this.teaserText = input('', /* @ts-ignore */
|
|
72
|
+
...(ngDevMode ? [{ debugName: "teaserText" }] : /* istanbul ignore next */ []));
|
|
64
73
|
/**
|
|
65
74
|
* Emits an event when the slide is clicked.
|
|
66
75
|
*/
|
|
@@ -88,7 +97,7 @@ class EclCarouselItemComponent extends ECLBaseDirective {
|
|
|
88
97
|
this.slideClick.emit(new EclSlideEvent(this));
|
|
89
98
|
}
|
|
90
99
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: EclCarouselItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
91
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.2", type: EclCarouselItemComponent, isStandalone: true, selector: "ecl-carousel-item", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, isCurrentInput: { classPropertyName: "isCurrentInput", publicName: "isCurrent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { slideClick: "slideClick" }, host: { attributes: { "role": "tabpanel" }, listeners: { "click": "onSlideClick()" }, properties: { "attr.aria-label": "ariaLabel()", "attr.aria-roledescription": "\"slide\"", "attr.inert": "!isCurrent() || undefined", "class": "cssClasses()" } }, queries: [{ propertyName: "banner", first: true, predicate: i0.forwardRef(() => EclBannerComponent), descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<ng-content></ng-content>\n", styles: [":host:before{left:0!important;display:none}\n"] }); }
|
|
100
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.2", type: EclCarouselItemComponent, isStandalone: true, selector: "ecl-carousel-item", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, isCurrentInput: { classPropertyName: "isCurrentInput", publicName: "isCurrent", isSignal: true, isRequired: false, transformFunction: null }, teaserText: { classPropertyName: "teaserText", publicName: "teaserText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { slideClick: "slideClick" }, host: { attributes: { "role": "tabpanel" }, listeners: { "click": "onSlideClick()" }, properties: { "attr.aria-label": "ariaLabel()", "attr.aria-roledescription": "\"slide\"", "attr.inert": "!isCurrent() || undefined", "class": "cssClasses()" } }, queries: [{ propertyName: "banner", first: true, predicate: i0.forwardRef(() => EclBannerComponent), descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<ng-content></ng-content>\n", styles: [":host:before{left:0!important;display:none}\n"] }); }
|
|
92
101
|
}
|
|
93
102
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: EclCarouselItemComponent, decorators: [{
|
|
94
103
|
type: Component,
|
|
@@ -100,22 +109,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
|
|
|
100
109
|
'[class]': 'cssClasses()',
|
|
101
110
|
'(click)': 'onSlideClick()',
|
|
102
111
|
}, template: "<ng-content></ng-content>\n", styles: [":host:before{left:0!important;display:none}\n"] }]
|
|
103
|
-
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], isCurrentInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCurrent", required: false }] }], slideClick: [{ type: i0.Output, args: ["slideClick"] }], banner: [{ type: i0.ContentChild, args: [forwardRef(() => EclBannerComponent), { isSignal: true }] }] } });
|
|
112
|
+
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], isCurrentInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCurrent", required: false }] }], teaserText: [{ type: i0.Input, args: [{ isSignal: true, alias: "teaserText", required: false }] }], slideClick: [{ type: i0.Output, args: ["slideClick"] }], banner: [{ type: i0.ContentChild, args: [forwardRef(() => EclBannerComponent), { isSignal: true }] }] } });
|
|
104
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Number of teaser buttons visible at once on the desktop breakpoint.
|
|
116
|
+
*/
|
|
117
|
+
const VISIBLE_TEASERS = 4;
|
|
105
118
|
/**
|
|
106
119
|
* Carousel (slideshow) component following the ECL design system.
|
|
107
120
|
* Renders a sequence of `EclCarouselItemComponent` slides with autoplay, keyboard navigation,
|
|
108
121
|
* responsive breakpoint awareness, and RTL layout support.
|
|
109
122
|
*
|
|
123
|
+
* The slide engine is powered by Embla (via `embla-carousel-angular`), matching the upstream
|
|
124
|
+
* ECL implementation: an infinite `loop`, the `Autoplay` plugin driving the teaser loading bars,
|
|
125
|
+
* and a slider pager with previous/next/play/pause controls, a counter, and a tablist of teaser
|
|
126
|
+
* buttons (one per slide).
|
|
127
|
+
*
|
|
110
128
|
* - Selector: `ecl-carousel`.
|
|
111
129
|
* - Project `ecl-carousel-item` elements as slides (also via `contentChildren`).
|
|
112
|
-
* - `ariaLabel` — ARIA label for the carousel landmark; aliased as `aria-label
|
|
130
|
+
* - `ariaLabel` — ARIA label for the carousel landmark; aliased as `aria-label`.
|
|
113
131
|
* - `isAutoplay` — enables automatic slide advancement; boolean attribute.
|
|
114
132
|
* - `playInterval` — milliseconds between automatic transitions; default `5000`.
|
|
133
|
+
* - `counterLabel` — separator label used by the counter, defaults to `ecl.common.OF`.
|
|
115
134
|
* - `slide` — emits `EclSlideEvent` when the active slide changes.
|
|
116
|
-
* - `isPlaying` / `isPausedForced` — public flags reflecting playback state.
|
|
117
|
-
* - `isRtl` — public flag reflecting RTL mode.
|
|
118
|
-
* - Navigation controls and play/pause buttons are rendered internally.
|
|
119
135
|
*/
|
|
120
136
|
class EclCarouselComponent extends ECLBaseDirective {
|
|
121
137
|
constructor() {
|
|
@@ -132,12 +148,18 @@ class EclCarouselComponent extends ECLBaseDirective {
|
|
|
132
148
|
* Interval in milliseconds between automatic slide transitions.
|
|
133
149
|
*/
|
|
134
150
|
this.playInterval = input(5000, { ...(ngDevMode ? { debugName: "playInterval" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
151
|
+
/**
|
|
152
|
+
* Separator label used by the counter, e.g. the "of" in "1 of 5".
|
|
153
|
+
* Defaults to the translated `ecl.common.OF` key.
|
|
154
|
+
*/
|
|
155
|
+
this.counterLabel = input('', /* @ts-ignore */
|
|
156
|
+
...(ngDevMode ? [{ debugName: "counterLabel" }] : /* istanbul ignore next */ []));
|
|
135
157
|
/**
|
|
136
158
|
* Event emitted when a slide change occurs.
|
|
137
159
|
*/
|
|
138
160
|
this.slide = output();
|
|
139
161
|
/**
|
|
140
|
-
* Reference to the main carousel
|
|
162
|
+
* Reference to the main carousel viewport element (the Embla root).
|
|
141
163
|
*/
|
|
142
164
|
this.carouselContainerElement = viewChild('carouselContainer', /* @ts-ignore */
|
|
143
165
|
...(ngDevMode ? [{ debugName: "carouselContainerElement" }] : /* istanbul ignore next */ []));
|
|
@@ -147,58 +169,90 @@ class EclCarouselComponent extends ECLBaseDirective {
|
|
|
147
169
|
this.slides = contentChildren(forwardRef(() => EclCarouselItemComponent), /* @ts-ignore */
|
|
148
170
|
...(ngDevMode ? [{ debugName: "slides" }] : /* istanbul ignore next */ []));
|
|
149
171
|
/**
|
|
150
|
-
*
|
|
172
|
+
* Indicates whether autoplay is currently active.
|
|
151
173
|
*/
|
|
152
|
-
this.
|
|
153
|
-
...(ngDevMode ? [{ debugName: "
|
|
174
|
+
this.isPlaying = signal(false, /* @ts-ignore */
|
|
175
|
+
...(ngDevMode ? [{ debugName: "isPlaying" }] : /* istanbul ignore next */ []));
|
|
154
176
|
/**
|
|
155
|
-
*
|
|
177
|
+
* Indicates whether autoplay was manually paused (via the pause button or
|
|
178
|
+
* on mobile). When paused this way the loading bar is cleared.
|
|
156
179
|
*/
|
|
157
|
-
this.
|
|
158
|
-
...(ngDevMode ? [{ debugName: "
|
|
180
|
+
this.isPausedForced = signal(false, /* @ts-ignore */
|
|
181
|
+
...(ngDevMode ? [{ debugName: "isPausedForced" }] : /* istanbul ignore next */ []));
|
|
159
182
|
/**
|
|
160
|
-
*
|
|
183
|
+
* Indicates whether the layout is currently in RTL mode.
|
|
161
184
|
*/
|
|
162
|
-
this.
|
|
163
|
-
...(ngDevMode ? [{ debugName: "pauseButton" }] : /* istanbul ignore next */ []));
|
|
185
|
+
this.isRtl = false;
|
|
164
186
|
/**
|
|
165
|
-
*
|
|
187
|
+
* Zero-based index of the currently active slide (mirrors Embla's selected snap).
|
|
166
188
|
*/
|
|
167
|
-
this.
|
|
168
|
-
...(ngDevMode ? [{ debugName: "firstHiddenItem" }] : /* istanbul ignore next */ []));
|
|
189
|
+
this.activeIndex = 0;
|
|
169
190
|
/**
|
|
170
|
-
*
|
|
191
|
+
* Start index of the currently visible teaser window (desktop only).
|
|
171
192
|
*/
|
|
172
|
-
this.
|
|
173
|
-
...(ngDevMode ? [{ debugName: "lastHiddenItem" }] : /* istanbul ignore next */ []));
|
|
193
|
+
this.teaserWindowStart = 0;
|
|
174
194
|
/**
|
|
175
|
-
*
|
|
195
|
+
* Indicates whether autoplay is temporarily paused by hover/focus. Unlike a
|
|
196
|
+
* manual pause, the loading bar stays visible and frozen at its current
|
|
197
|
+
* progress (matching ECL), resuming when the pointer/focus leaves.
|
|
176
198
|
*/
|
|
177
|
-
this.
|
|
178
|
-
...(ngDevMode ? [{ debugName: "
|
|
199
|
+
this.isHoverPaused = signal(false, /* @ts-ignore */
|
|
200
|
+
...(ngDevMode ? [{ debugName: "isHoverPaused" }] : /* istanbul ignore next */ []));
|
|
179
201
|
/**
|
|
180
|
-
*
|
|
202
|
+
* Whether the current viewport is at the mobile breakpoint. A signal so the
|
|
203
|
+
* template can react (the pager drops play/pause on mobile, matching ECL,
|
|
204
|
+
* where play/pause is a desktop-only control).
|
|
181
205
|
*/
|
|
182
|
-
this.
|
|
206
|
+
this.isMobileBreakpoint = signal(false, /* @ts-ignore */
|
|
207
|
+
...(ngDevMode ? [{ debugName: "isMobileBreakpoint" }] : /* istanbul ignore next */ []));
|
|
183
208
|
/**
|
|
184
|
-
*
|
|
209
|
+
* Whether the viewport is at or above the ECL desktop breakpoint (1140px).
|
|
210
|
+
* This is the boundary the ECL CSS uses to show the play/pause controls and
|
|
211
|
+
* the teaser tablist (and to hide the mobile dots). A signal so the pager
|
|
212
|
+
* variant can react: play/pause only renders at/above this width, matching
|
|
213
|
+
* the CSS so the pause button never leaks onto narrower viewports.
|
|
185
214
|
*/
|
|
186
|
-
this.
|
|
215
|
+
this.isDesktopBreakpointActive = signal(false, /* @ts-ignore */
|
|
216
|
+
...(ngDevMode ? [{ debugName: "isDesktopBreakpointActive" }] : /* istanbul ignore next */ []));
|
|
187
217
|
/**
|
|
188
|
-
*
|
|
218
|
+
* Embla options, mirroring the upstream ECL configuration.
|
|
189
219
|
*/
|
|
190
|
-
this.
|
|
220
|
+
this.emblaOptions = {
|
|
221
|
+
loop: true,
|
|
222
|
+
align: 'start',
|
|
223
|
+
duration: 20,
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Embla plugins. Built once so the Embla directive does not needlessly re-init.
|
|
227
|
+
*/
|
|
228
|
+
this.emblaPlugins = [];
|
|
191
229
|
/**
|
|
192
|
-
*
|
|
230
|
+
* Embla events the component reacts to.
|
|
193
231
|
*/
|
|
194
|
-
this.
|
|
195
|
-
|
|
196
|
-
|
|
232
|
+
this.emblaEvents = [
|
|
233
|
+
'select',
|
|
234
|
+
'reInit',
|
|
235
|
+
'autoplay:timerset',
|
|
236
|
+
];
|
|
237
|
+
/**
|
|
238
|
+
* Reference to the Embla directive driving the slide engine.
|
|
239
|
+
*/
|
|
240
|
+
this.embla = viewChild('embla', /* @ts-ignore */
|
|
241
|
+
...(ngDevMode ? [{ debugName: "embla" }] : /* istanbul ignore next */ []));
|
|
242
|
+
/**
|
|
243
|
+
* References to the teaser navigation buttons corresponding to each slide.
|
|
244
|
+
*/
|
|
245
|
+
this.teaserButtons = viewChildren('teaserButton', /* @ts-ignore */
|
|
246
|
+
...(ngDevMode ? [{ debugName: "teaserButtons" }] : /* istanbul ignore next */ []));
|
|
247
|
+
/**
|
|
248
|
+
* References to the teaser loading-bar completion elements (one per slide),
|
|
249
|
+
* used to restart the progress animation when the active slide changes.
|
|
250
|
+
*/
|
|
251
|
+
this.loadingBars = viewChildren('loadingBar', /* @ts-ignore */
|
|
252
|
+
...(ngDevMode ? [{ debugName: "loadingBars" }] : /* istanbul ignore next */ []));
|
|
197
253
|
this.isTabletBreakpoint = false;
|
|
198
|
-
this.isDesktopBreakpoint = false;
|
|
199
254
|
this.isLargeBreakpoint = false;
|
|
200
|
-
this.
|
|
201
|
-
this.stopSubject = new Subject();
|
|
255
|
+
this.isDesktopBreakpoint = false;
|
|
202
256
|
this.destroy$ = new Subject();
|
|
203
257
|
/**
|
|
204
258
|
* Debounce interval (ms) used internally for timing and transitions.
|
|
@@ -208,8 +262,14 @@ class EclCarouselComponent extends ECLBaseDirective {
|
|
|
208
262
|
this.cd = inject(ChangeDetectorRef);
|
|
209
263
|
this.renderer2 = inject(Renderer2);
|
|
210
264
|
this.eclRtlService = inject(EclRtlService);
|
|
265
|
+
this.translate = inject(TranslateService);
|
|
211
266
|
this.document = inject(DOCUMENT);
|
|
212
|
-
this.window = this.document.defaultView;
|
|
267
|
+
this.window = this.document.defaultView;
|
|
268
|
+
/**
|
|
269
|
+
* Host element, used to locate the slider pager's play/pause buttons by their
|
|
270
|
+
* ECL classes for focus management after a play/pause toggle.
|
|
271
|
+
*/
|
|
272
|
+
this.hostEl = inject(ElementRef);
|
|
213
273
|
/**
|
|
214
274
|
* Returns the computed CSS class list for the component.
|
|
215
275
|
*/
|
|
@@ -217,25 +277,39 @@ class EclCarouselComponent extends ECLBaseDirective {
|
|
|
217
277
|
...(ngDevMode ? [{ debugName: "cssClasses" }] : /* istanbul ignore next */ []));
|
|
218
278
|
afterNextRender(() => {
|
|
219
279
|
this.observeElementSizeChanges();
|
|
220
|
-
this.cd.detectChanges();
|
|
221
280
|
this.observeBreakpointChanges();
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
this.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.cd.detectChanges();
|
|
229
|
-
});
|
|
230
|
-
this.observeElementSizeChanges();
|
|
231
|
-
});
|
|
281
|
+
this.syncFromEmbla();
|
|
282
|
+
if (this.window) {
|
|
283
|
+
fromEvent(this.window, 'resize')
|
|
284
|
+
.pipe(auditTime(50), takeUntil(this.destroy$))
|
|
285
|
+
.subscribe(() => this.observeElementSizeChanges());
|
|
286
|
+
}
|
|
232
287
|
});
|
|
233
288
|
}
|
|
234
289
|
/**
|
|
235
|
-
* Angular lifecycle hook: initializes
|
|
290
|
+
* Angular lifecycle hook: initializes RTL detection and the Embla plugins.
|
|
236
291
|
*/
|
|
237
292
|
ngOnInit() {
|
|
238
|
-
this.
|
|
293
|
+
this.emblaOptions = {
|
|
294
|
+
...this.emblaOptions,
|
|
295
|
+
direction: this.isRtl ? 'rtl' : 'ltr',
|
|
296
|
+
};
|
|
297
|
+
const autoplayDesired = this.isAutoplayDesired();
|
|
298
|
+
// Reflect the initial play state so the current slide's loading bar is
|
|
299
|
+
// shown on load (the plugin self-starts via playOnInit).
|
|
300
|
+
this.isPlaying.set(autoplayDesired);
|
|
301
|
+
this.emblaPlugins = [
|
|
302
|
+
Autoplay({
|
|
303
|
+
delay: this.playInterval(),
|
|
304
|
+
// Autoplay is on by default (matching ECL) unless explicitly
|
|
305
|
+
// disabled via [isAutoplay]="false". Letting the plugin start
|
|
306
|
+
// itself avoids a race with Embla's own initialization.
|
|
307
|
+
playOnInit: autoplayDesired,
|
|
308
|
+
stopOnInteraction: false,
|
|
309
|
+
stopOnMouseEnter: true,
|
|
310
|
+
stopOnFocusIn: true,
|
|
311
|
+
}),
|
|
312
|
+
];
|
|
239
313
|
this.handleRtlChangeState();
|
|
240
314
|
}
|
|
241
315
|
/**
|
|
@@ -243,447 +317,377 @@ class EclCarouselComponent extends ECLBaseDirective {
|
|
|
243
317
|
*/
|
|
244
318
|
ngAfterContentInit() {
|
|
245
319
|
setTimeout(() => {
|
|
246
|
-
this.
|
|
320
|
+
this.initSlides();
|
|
247
321
|
});
|
|
248
322
|
}
|
|
249
323
|
/**
|
|
250
|
-
* Angular lifecycle hook: cleans up subscriptions
|
|
324
|
+
* Angular lifecycle hook: cleans up subscriptions.
|
|
251
325
|
*/
|
|
252
326
|
ngOnDestroy() {
|
|
253
327
|
this.destroy$.next(true);
|
|
254
328
|
this.destroy$.unsubscribe();
|
|
255
|
-
if (this.unsubscribeListener) {
|
|
256
|
-
this.unsubscribeListener();
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Starts autoplay and focuses the pause button.
|
|
261
|
-
*/
|
|
262
|
-
onPlayClick() {
|
|
263
|
-
this.startPlaying();
|
|
264
|
-
this.focusOnButton(this.pauseButton());
|
|
265
329
|
}
|
|
266
330
|
/**
|
|
267
|
-
*
|
|
268
|
-
*/
|
|
269
|
-
onPauseClick() {
|
|
270
|
-
this.stopPlaying();
|
|
271
|
-
this.focusOnButton(this.playButton());
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Handles key press on the first carousel element to stop autoplay if TAB is pressed.
|
|
275
|
-
*/
|
|
276
|
-
onFirstCarouselElementPress(e) {
|
|
277
|
-
if (e.key === KeyCode.TAB) {
|
|
278
|
-
if (this.isPlaying) {
|
|
279
|
-
this.stopPlaying();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Handles keyboard interaction on the play button.
|
|
285
|
-
*/
|
|
286
|
-
onPlayKeyboardPress(e) {
|
|
287
|
-
if (e.key === KeyCode.TAB && e.shiftKey) {
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
if ([KeyCode.TAB].includes(e.key)) {
|
|
291
|
-
if (this.isPlaying) {
|
|
292
|
-
this.focusOnButton(this.navigationButtons().at(this.getCurrentSlideIndex()));
|
|
293
|
-
this.stopPlaying();
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
this.focusOnButton(this.navigationButtons().at(this.getCurrentSlideIndex()));
|
|
297
|
-
}
|
|
298
|
-
e.preventDefault();
|
|
299
|
-
}
|
|
300
|
-
else if (!this.isPlaying && [KeyCode.ARROW_RIGHT].includes(e.key)) {
|
|
301
|
-
this.focusOnButton(this.navigationButtons().at(this.getCurrentSlideIndex()));
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Handles keyboard navigation for the navigation buttons.
|
|
331
|
+
* Returns whether the carousel has padding applied (e.g., on desktop breakpoint).
|
|
306
332
|
*/
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this.onNavigationButtonArrowRight(e);
|
|
310
|
-
}
|
|
311
|
-
else if (e.key === KeyCode.ARROW_LEFT) {
|
|
312
|
-
this.onNavigationButtonArrowLeft(e, index);
|
|
313
|
-
}
|
|
314
|
-
return;
|
|
333
|
+
isPadded() {
|
|
334
|
+
return this.isDesktopBreakpoint;
|
|
315
335
|
}
|
|
316
336
|
/**
|
|
317
|
-
* Checks whether
|
|
337
|
+
* Checks whether there are enough slides to render controls.
|
|
318
338
|
*/
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
return button.nativeElement.style.display === '';
|
|
339
|
+
hasControls() {
|
|
340
|
+
return this.getSlidesCount() > 1;
|
|
324
341
|
}
|
|
325
342
|
/**
|
|
326
|
-
*
|
|
343
|
+
* Retrieves the current active slide index (zero-based).
|
|
327
344
|
*/
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
button.nativeElement.focus();
|
|
345
|
+
getCurrentSlideIndex() {
|
|
346
|
+
return this.activeIndex;
|
|
333
347
|
}
|
|
334
348
|
/**
|
|
335
|
-
*
|
|
349
|
+
* Returns the 1-based position of the active slide (for the counter).
|
|
336
350
|
*/
|
|
337
|
-
|
|
338
|
-
this.
|
|
339
|
-
this.activateSlide(slide, true);
|
|
351
|
+
getCounterCurrent() {
|
|
352
|
+
return this.activeIndex + 1;
|
|
340
353
|
}
|
|
341
354
|
/**
|
|
342
|
-
*
|
|
355
|
+
* Returns the total number of slides (for the counter).
|
|
343
356
|
*/
|
|
344
|
-
|
|
345
|
-
this.
|
|
346
|
-
this.goToNextSlide(true);
|
|
357
|
+
getCounterTotal() {
|
|
358
|
+
return this.getSlidesCount();
|
|
347
359
|
}
|
|
348
360
|
/**
|
|
349
|
-
*
|
|
361
|
+
* Dot indices for the mobile slider pager (one dot per slide). The dots are
|
|
362
|
+
* hidden on desktop via the ECL CSS (`.ecl-carousel__dots { display: none }`)
|
|
363
|
+
* and shown at the mobile breakpoint.
|
|
350
364
|
*/
|
|
351
|
-
|
|
352
|
-
this.
|
|
353
|
-
this.goToPreviousSlide(true);
|
|
365
|
+
getDotIndices() {
|
|
366
|
+
return Array.from({ length: this.getSlidesCount() }, (_unused, dotIndex) => dotIndex);
|
|
354
367
|
}
|
|
355
368
|
/**
|
|
356
|
-
*
|
|
369
|
+
* Pre-translated accessible label for each pager dot. Translated here since
|
|
370
|
+
* the shared `ecl-slider-pager` receives plain strings.
|
|
357
371
|
*/
|
|
358
|
-
|
|
359
|
-
this.
|
|
372
|
+
getDotAriaLabels() {
|
|
373
|
+
const total = this.getSlidesCount();
|
|
374
|
+
return Array.from({ length: total }, (_unused, dotIndex) => this.translate.instant('ecl.carousel.GO-TO-SLIDE', {
|
|
375
|
+
current: dotIndex + 1,
|
|
376
|
+
total,
|
|
377
|
+
}));
|
|
360
378
|
}
|
|
361
379
|
/**
|
|
362
|
-
*
|
|
380
|
+
* Returns whether a teaser button should be visible in the current window.
|
|
363
381
|
*/
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
382
|
+
isTeaserVisible(index) {
|
|
383
|
+
const count = this.getSlidesCount();
|
|
384
|
+
if (count <= VISIBLE_TEASERS) {
|
|
385
|
+
return true;
|
|
367
386
|
}
|
|
387
|
+
const position = ((index - this.teaserWindowStart) % count + count) % count;
|
|
388
|
+
return position < VISIBLE_TEASERS;
|
|
368
389
|
}
|
|
369
390
|
/**
|
|
370
|
-
* Returns
|
|
391
|
+
* Returns the flex order of a teaser button within the visible window.
|
|
371
392
|
*/
|
|
372
|
-
|
|
373
|
-
|
|
393
|
+
getTeaserOrder(index) {
|
|
394
|
+
const count = this.getSlidesCount();
|
|
395
|
+
return ((index - this.teaserWindowStart) % count + count) % count;
|
|
374
396
|
}
|
|
375
397
|
/**
|
|
376
|
-
*
|
|
398
|
+
* Whether the pager should show the "pause" control (i.e. autoplay is
|
|
399
|
+
* actively running). While the pointer/focus hover-pauses the carousel the
|
|
400
|
+
* timer is suspended, so the pager must show "play" even though a manual
|
|
401
|
+
* pause was not triggered. Mirrors upstream ECL, where hovering flips the
|
|
402
|
+
* control to play.
|
|
377
403
|
*/
|
|
378
|
-
|
|
379
|
-
return this.
|
|
404
|
+
isPagerPlaying() {
|
|
405
|
+
return this.isPlaying() && !this.isHoverPaused();
|
|
380
406
|
}
|
|
381
407
|
/**
|
|
382
|
-
*
|
|
408
|
+
* Pager variant by breakpoint. Below the ECL desktop breakpoint (1140px)
|
|
409
|
+
* the carousel shows only the dots navigation (prev / dots / next);
|
|
410
|
+
* play/pause is a desktop-only control in ECL. Rendering `'dots'` there
|
|
411
|
+
* avoids the pager's inline play/pause `display` style (which would
|
|
412
|
+
* otherwise beat the ECL CSS and leave the pause button visible on narrower
|
|
413
|
+
* viewports). At/above 1140px the dots are hidden by CSS, so
|
|
414
|
+
* `'play-pause-dots'` yields play/pause + prev/next + teasers.
|
|
415
|
+
*
|
|
416
|
+
* The 1140px boundary mirrors the media query the ECL CSS uses to toggle
|
|
417
|
+
* `.ecl-carousel__play/__pause` and `.ecl-carousel__teasers`.
|
|
383
418
|
*/
|
|
384
|
-
|
|
385
|
-
return this.
|
|
419
|
+
pagerVariant() {
|
|
420
|
+
return this.isDesktopBreakpointActive() ? 'play-pause-dots' : 'dots';
|
|
386
421
|
}
|
|
387
422
|
/**
|
|
388
|
-
*
|
|
389
|
-
* Corrects index for internal structure with hidden buffer slides.
|
|
423
|
+
* Handles the throttled Embla events the component subscribed to.
|
|
390
424
|
*/
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
425
|
+
onEmblaEvent(event) {
|
|
426
|
+
switch (event) {
|
|
427
|
+
case 'select':
|
|
428
|
+
case 'reInit':
|
|
429
|
+
this.syncFromEmbla();
|
|
430
|
+
break;
|
|
431
|
+
case 'autoplay:timerset':
|
|
432
|
+
// Fired each time the autoplay timer (re)starts for the current
|
|
433
|
+
// slide; replay the loading-bar fill animation from zero.
|
|
434
|
+
this.restartActiveLoadingBar();
|
|
435
|
+
break;
|
|
436
|
+
default:
|
|
437
|
+
break;
|
|
400
438
|
}
|
|
401
|
-
else if (currentIndex === 0) {
|
|
402
|
-
return this.getSlidesCount() - 1;
|
|
403
|
-
}
|
|
404
|
-
return currentIndex - 1;
|
|
405
439
|
}
|
|
406
440
|
/**
|
|
407
|
-
*
|
|
441
|
+
* Starts autoplay.
|
|
442
|
+
*
|
|
443
|
+
* @param moveFocus when true (a user clicked play) focus moves to the pause
|
|
444
|
+
* button. Auto-starts (e.g. on breakpoint changes) pass false so the
|
|
445
|
+
* page is not scrolled to the carousel on load.
|
|
408
446
|
*/
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
447
|
+
onPlayClick(moveFocus = true) {
|
|
448
|
+
this.getAutoplay()?.play();
|
|
449
|
+
this.isPlaying.set(true);
|
|
450
|
+
this.isPausedForced.set(false);
|
|
451
|
+
this.isHoverPaused.set(false);
|
|
452
|
+
this.cd.detectChanges();
|
|
453
|
+
if (moveFocus) {
|
|
454
|
+
this.focusPagerButton('.ecl-carousel__pause');
|
|
455
|
+
}
|
|
417
456
|
}
|
|
418
457
|
/**
|
|
419
|
-
*
|
|
458
|
+
* Stops autoplay and focuses the play button.
|
|
420
459
|
*/
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
460
|
+
onPauseClick() {
|
|
461
|
+
// Pause (not full stop): halt the autoplay timer but keep the active
|
|
462
|
+
// teaser loading bar visible, frozen at its current progress (matching
|
|
463
|
+
// upstream ECL's `handleAutoPlay(false, true)`). The installed
|
|
464
|
+
// embla-carousel-autoplay exposes no `pause()`, so we call `stop()`
|
|
465
|
+
// WITHOUT `reset()` — the frozen bar is driven by our `is-paused` CSS
|
|
466
|
+
// class, not the plugin. A full stop+reset (which clears the bar) only
|
|
467
|
+
// happens on prev/next/teaser navigation via `stopAutoplay()`.
|
|
468
|
+
this.getAutoplay()?.stop();
|
|
469
|
+
this.isPlaying.set(false);
|
|
470
|
+
this.isPausedForced.set(true);
|
|
471
|
+
this.isHoverPaused.set(false);
|
|
472
|
+
this.cd.detectChanges();
|
|
473
|
+
this.focusPagerButton('.ecl-carousel__play');
|
|
425
474
|
}
|
|
426
475
|
/**
|
|
427
|
-
*
|
|
476
|
+
* Navigates to the next slide and stops autoplay.
|
|
428
477
|
*/
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
start$
|
|
433
|
-
.pipe(switchMap(() => interval(this.playInterval()).pipe(takeUntil(stop$))), takeUntil(this.destroy$))
|
|
434
|
-
.subscribe(() => {
|
|
435
|
-
this.goToNextSlide(true);
|
|
436
|
-
});
|
|
478
|
+
onNextSlideClick() {
|
|
479
|
+
this.stopAutoplay();
|
|
480
|
+
this.embla()?.scrollNext();
|
|
437
481
|
}
|
|
438
482
|
/**
|
|
439
|
-
*
|
|
483
|
+
* Navigates to the previous slide and stops autoplay.
|
|
440
484
|
*/
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
this.
|
|
444
|
-
if (item && item.isCurrent()) {
|
|
445
|
-
result = index;
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
return result;
|
|
485
|
+
onPreviousSlideClick() {
|
|
486
|
+
this.stopAutoplay();
|
|
487
|
+
this.embla()?.scrollPrev();
|
|
449
488
|
}
|
|
450
489
|
/**
|
|
451
|
-
*
|
|
490
|
+
* Handles user interaction with a teaser button: activates the matching slide.
|
|
452
491
|
*/
|
|
453
|
-
|
|
454
|
-
this.
|
|
455
|
-
|
|
456
|
-
});
|
|
457
|
-
if (this.hasControls()) {
|
|
458
|
-
this.initHiddenItems();
|
|
459
|
-
if (this.isNoSlideSelected()) {
|
|
460
|
-
this.activateSlide(this.slides().at(0), false);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
492
|
+
onTeaserClick(index) {
|
|
493
|
+
this.stopAutoplay();
|
|
494
|
+
this.embla()?.scrollTo(index);
|
|
463
495
|
}
|
|
464
496
|
/**
|
|
465
|
-
*
|
|
497
|
+
* Handles a dot activation from the mobile slider pager: scrolls to the
|
|
498
|
+
* matching slide and stops autoplay (same behavior as a teaser click).
|
|
466
499
|
*/
|
|
467
|
-
|
|
468
|
-
this.
|
|
469
|
-
this.
|
|
470
|
-
this.lastHiddenItem().el.nativeElement.innerHTML = this.getFirstContentChild();
|
|
471
|
-
this.lastHiddenItem().isCurrent.set(false);
|
|
472
|
-
this.unsubscribeListener = this.renderer2.listen(this.slidesContainer().nativeElement, 'transitionend', () => this.checkIndex());
|
|
500
|
+
onDotClick(index) {
|
|
501
|
+
this.stopAutoplay();
|
|
502
|
+
this.embla()?.scrollTo(index);
|
|
473
503
|
}
|
|
474
504
|
/**
|
|
475
|
-
*
|
|
505
|
+
* Handles keyboard navigation (ArrowLeft/ArrowRight) across teaser buttons.
|
|
476
506
|
*/
|
|
477
|
-
|
|
478
|
-
if (
|
|
479
|
-
|
|
480
|
-
this.activateSlide(item, false);
|
|
507
|
+
onTeaserKeyboardPress(event, index) {
|
|
508
|
+
if (event.key !== KeyCode.ARROW_RIGHT && event.key !== KeyCode.ARROW_LEFT) {
|
|
509
|
+
return;
|
|
481
510
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
511
|
+
event.preventDefault();
|
|
512
|
+
const direction = event.key === KeyCode.ARROW_RIGHT ? 1 : -1;
|
|
513
|
+
const count = this.getSlidesCount();
|
|
514
|
+
const nextIndex = (index + direction + count) % count;
|
|
515
|
+
this.stopAutoplay();
|
|
516
|
+
this.embla()?.scrollTo(nextIndex);
|
|
517
|
+
this.focusOnButton(this.teaserButtons()[nextIndex]);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Freezes the loading bar while the pointer/focus is over the carousel.
|
|
521
|
+
* The Embla autoplay plugin pauses the timer itself (stopOnMouseEnter /
|
|
522
|
+
* stopOnFocusIn); this only mirrors that as a visible paused state.
|
|
523
|
+
*/
|
|
524
|
+
onViewportPause() {
|
|
525
|
+
if (this.isPlaying() && !this.isPausedForced()) {
|
|
526
|
+
this.isHoverPaused.set(true);
|
|
527
|
+
this.cd.detectChanges();
|
|
485
528
|
}
|
|
486
529
|
}
|
|
487
530
|
/**
|
|
488
|
-
*
|
|
531
|
+
* Resumes the loading bar once the pointer/focus leaves the carousel.
|
|
489
532
|
*/
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
* Returns the inner HTML of the first real slide (used for buffer item).
|
|
495
|
-
*/
|
|
496
|
-
getFirstContentChild() {
|
|
497
|
-
return this.getSlidesCount() > 0 ? this.slides().at(0).el.nativeElement.innerHTML : '';
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* Sets up a slide with index and ARIA label.
|
|
501
|
-
*/
|
|
502
|
-
initSlide(item, idx) {
|
|
503
|
-
item.index.set(idx);
|
|
504
|
-
item.ariaLabel.set(`slide ${idx + 1} of ${this.getSlidesCount()}`);
|
|
505
|
-
const banner = item.banner();
|
|
506
|
-
if (banner) {
|
|
507
|
-
banner.skipHeightManagement = true;
|
|
533
|
+
onViewportResume() {
|
|
534
|
+
if (this.isHoverPaused()) {
|
|
535
|
+
this.isHoverPaused.set(false);
|
|
536
|
+
this.cd.detectChanges();
|
|
508
537
|
}
|
|
509
538
|
}
|
|
510
539
|
/**
|
|
511
|
-
*
|
|
512
|
-
*/
|
|
513
|
-
goToNextSlide(isLoop) {
|
|
514
|
-
this.activateSlide(this.getNextSlide(isLoop), true);
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Activates the previous slide, with option to loop.
|
|
518
|
-
*/
|
|
519
|
-
goToPreviousSlide(isLoop) {
|
|
520
|
-
this.activateSlide(this.getPreviousSlide(isLoop), true);
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Activates a specific slide and triggers slide change event if needed.
|
|
540
|
+
* Sets focus on the specified button.
|
|
524
541
|
*/
|
|
525
|
-
|
|
526
|
-
if (
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
else {
|
|
530
|
-
this.transitionDuration = 0;
|
|
531
|
-
}
|
|
532
|
-
if (slideItem) {
|
|
533
|
-
this.getAllSlidesArray().forEach((item) => {
|
|
534
|
-
item.isCurrent.set(false);
|
|
535
|
-
});
|
|
536
|
-
slideItem.isCurrent.set(true);
|
|
537
|
-
}
|
|
538
|
-
this.cd.detectChanges();
|
|
539
|
-
if (withTransition) {
|
|
540
|
-
this.slide.emit(new EclSlideEvent(this.slides().at(this.getCurrentSlideIndex())));
|
|
542
|
+
focusOnButton(button) {
|
|
543
|
+
if (!button) {
|
|
544
|
+
return;
|
|
541
545
|
}
|
|
546
|
+
button.nativeElement.focus();
|
|
542
547
|
}
|
|
543
548
|
/**
|
|
544
|
-
*
|
|
549
|
+
* Focuses the slider pager button matching the given ECL class selector
|
|
550
|
+
* (e.g. `.ecl-carousel__play`). Used after a play/pause toggle so focus
|
|
551
|
+
* follows the now-visible control, since the pager swaps play/pause
|
|
552
|
+
* visibility via CSS. No-op if the button is not rendered (e.g. mobile).
|
|
545
553
|
*/
|
|
546
|
-
|
|
547
|
-
const
|
|
548
|
-
|
|
554
|
+
focusPagerButton(selector) {
|
|
555
|
+
const button = this.hostEl.nativeElement.querySelector(selector);
|
|
556
|
+
button?.focus();
|
|
549
557
|
}
|
|
550
558
|
/**
|
|
551
|
-
*
|
|
559
|
+
* Returns the Embla autoplay plugin instance, but only when the carousel
|
|
560
|
+
* actually has more than one scroll snap.
|
|
561
|
+
*
|
|
562
|
+
* The `embla-carousel-autoplay` plugin skips its own initialization when
|
|
563
|
+
* `scrollSnapList().length <= 1` (see the plugin's `init`), which leaves its
|
|
564
|
+
* internal `delay` lookup table undefined. Calling `play()` in that state
|
|
565
|
+
* throws (`delay[selectedScrollSnap()]` -> reading `0` of undefined). This
|
|
566
|
+
* happens whenever Embla cannot lay out multiple snaps — notably in a jsdom
|
|
567
|
+
* test or during SSR. Gating on the same `> 1` condition the plugin uses
|
|
568
|
+
* keeps autoplay a safe no-op until the carousel is genuinely slideable.
|
|
552
569
|
*/
|
|
553
|
-
|
|
554
|
-
|
|
570
|
+
getAutoplay() {
|
|
571
|
+
const api = this.embla()?.emblaApi;
|
|
572
|
+
if (!api || api.scrollSnapList().length <= 1) {
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
return api.plugins()?.autoplay;
|
|
555
576
|
}
|
|
556
577
|
/**
|
|
557
|
-
*
|
|
578
|
+
* Whether autoplay should run. On by default (matching ECL); only disabled
|
|
579
|
+
* when the consumer explicitly sets `[isAutoplay]="false"`.
|
|
558
580
|
*/
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
this.isPausedForced = false;
|
|
563
|
-
this.playSubject.next();
|
|
581
|
+
isAutoplayDesired() {
|
|
582
|
+
const isAutoplay = this.isAutoplay();
|
|
583
|
+
return isAutoplay || isAutoplay === undefined;
|
|
564
584
|
}
|
|
565
585
|
/**
|
|
566
|
-
* Stops autoplay
|
|
586
|
+
* Stops autoplay and resets it (mirrors ECL's `handleAutoPlay(true)`),
|
|
587
|
+
* clearing the loading-bar state.
|
|
567
588
|
*/
|
|
568
|
-
|
|
569
|
-
|
|
589
|
+
stopAutoplay() {
|
|
590
|
+
const autoplay = this.getAutoplay();
|
|
591
|
+
autoplay?.stop();
|
|
592
|
+
autoplay?.reset();
|
|
593
|
+
this.isPlaying.set(false);
|
|
594
|
+
this.isPausedForced.set(false);
|
|
595
|
+
this.isHoverPaused.set(false);
|
|
570
596
|
this.cd.detectChanges();
|
|
571
|
-
this.isPausedForced = true;
|
|
572
|
-
this.stopSubject.next();
|
|
573
597
|
}
|
|
574
598
|
/**
|
|
575
|
-
*
|
|
599
|
+
* Synchronizes component state from Embla's current selected snap:
|
|
600
|
+
* active index, slide `isCurrent` flags, teaser window and the slide event.
|
|
576
601
|
*/
|
|
577
|
-
|
|
578
|
-
|
|
602
|
+
syncFromEmbla() {
|
|
603
|
+
const api = this.embla()?.emblaApi;
|
|
604
|
+
if (!api) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const selected = api.selectedScrollSnap();
|
|
608
|
+
this.setActiveIndex(selected);
|
|
609
|
+
this.updateTeaserWindow();
|
|
610
|
+
this.slide.emit(new EclSlideEvent(this.slides().at(this.activeIndex)));
|
|
579
611
|
this.cd.detectChanges();
|
|
580
|
-
this.stopSubject.next();
|
|
581
612
|
}
|
|
582
613
|
/**
|
|
583
|
-
*
|
|
614
|
+
* Subscribes to RTL layout state changes to adapt carousel direction.
|
|
584
615
|
*/
|
|
585
|
-
|
|
586
|
-
this.
|
|
587
|
-
|
|
588
|
-
|
|
616
|
+
handleRtlChangeState() {
|
|
617
|
+
this.eclRtlService.currentRtlState$.pipe(takeUntil(this.destroy$)).subscribe((rtlState) => {
|
|
618
|
+
const changed = this.isRtl !== rtlState;
|
|
619
|
+
this.isRtl = rtlState;
|
|
620
|
+
if (changed) {
|
|
621
|
+
this.emblaOptions = {
|
|
622
|
+
...this.emblaOptions,
|
|
623
|
+
direction: this.isRtl ? 'rtl' : 'ltr',
|
|
624
|
+
};
|
|
625
|
+
this.cd.detectChanges();
|
|
626
|
+
}
|
|
627
|
+
});
|
|
589
628
|
}
|
|
590
629
|
/**
|
|
591
|
-
*
|
|
630
|
+
* Initializes slides (index, ARIA label, banner height management).
|
|
592
631
|
*/
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
632
|
+
initSlides() {
|
|
633
|
+
const slides = this.slides();
|
|
634
|
+
slides.forEach((item, idx) => {
|
|
635
|
+
item.index.set(idx);
|
|
636
|
+
item.ariaLabel.set(`slide ${idx + 1} of ${slides.length}`);
|
|
637
|
+
item.isCurrent.set(idx === 0);
|
|
638
|
+
const banner = item.banner();
|
|
639
|
+
if (banner) {
|
|
640
|
+
banner.skipHeightManagement = true;
|
|
602
641
|
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
return this.slides().at(currentIndex + 1);
|
|
606
|
-
}
|
|
642
|
+
});
|
|
643
|
+
this.cd.detectChanges();
|
|
607
644
|
}
|
|
608
645
|
/**
|
|
609
|
-
*
|
|
646
|
+
* Updates the active index and synchronizes slide active/inert state.
|
|
610
647
|
*/
|
|
611
|
-
|
|
612
|
-
|
|
648
|
+
setActiveIndex(index) {
|
|
649
|
+
this.activeIndex = index;
|
|
650
|
+
this.slides().forEach((item, idx) => {
|
|
651
|
+
item.isCurrent.set(idx === index);
|
|
652
|
+
});
|
|
613
653
|
}
|
|
614
654
|
/**
|
|
615
|
-
*
|
|
655
|
+
* Restarts the CSS progress animation on the active slide's loading bar.
|
|
656
|
+
* The animation uses `forwards`, so re-applying the `is-active` class alone
|
|
657
|
+
* will not replay it; the animation must be reset explicitly.
|
|
616
658
|
*/
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (isLoop) {
|
|
621
|
-
return this.getAllSlidesArray()[0];
|
|
622
|
-
}
|
|
623
|
-
else {
|
|
624
|
-
return this.slides().at(0);
|
|
625
|
-
}
|
|
659
|
+
restartActiveLoadingBar() {
|
|
660
|
+
if (!this.getAutoplay()?.isPlaying()) {
|
|
661
|
+
return;
|
|
626
662
|
}
|
|
627
|
-
|
|
628
|
-
|
|
663
|
+
const bar = this.loadingBars()[this.activeIndex]?.nativeElement;
|
|
664
|
+
if (!bar) {
|
|
665
|
+
return;
|
|
629
666
|
}
|
|
667
|
+
bar.style.animation = 'none';
|
|
668
|
+
// Force reflow so the browser registers the reset before re-enabling.
|
|
669
|
+
void bar.offsetWidth;
|
|
670
|
+
bar.style.animation = '';
|
|
630
671
|
}
|
|
631
672
|
/**
|
|
632
|
-
*
|
|
673
|
+
* Advances the visible teaser window when the active slide moves out of it.
|
|
633
674
|
*/
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
*/
|
|
640
|
-
getAllSlidesCount() {
|
|
641
|
-
if (this.hasControls()) {
|
|
642
|
-
return this.slides().length + 2;
|
|
675
|
+
updateTeaserWindow() {
|
|
676
|
+
const count = this.getSlidesCount();
|
|
677
|
+
if (count <= VISIBLE_TEASERS) {
|
|
678
|
+
this.teaserWindowStart = 0;
|
|
679
|
+
return;
|
|
643
680
|
}
|
|
644
|
-
|
|
645
|
-
|
|
681
|
+
const firstHiddenIndex = (this.teaserWindowStart + VISIBLE_TEASERS) % count;
|
|
682
|
+
if (this.activeIndex === firstHiddenIndex) {
|
|
683
|
+
this.teaserWindowStart = firstHiddenIndex;
|
|
646
684
|
}
|
|
647
685
|
}
|
|
648
686
|
/**
|
|
649
|
-
*
|
|
650
|
-
*/
|
|
651
|
-
isNoSlideSelected() {
|
|
652
|
-
return !this.slides().some((item) => item.isCurrent());
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Handles right arrow key navigation for buttons.
|
|
656
|
-
*/
|
|
657
|
-
onNavigationButtonArrowRight(e) {
|
|
658
|
-
setTimeout(() => {
|
|
659
|
-
this.goToNextSlide(false);
|
|
660
|
-
const button = this.navigationButtons().at(this.getCurrentSlideIndex());
|
|
661
|
-
this.focusOnButton(button);
|
|
662
|
-
}, this.TIMEOUT_INTERVAL);
|
|
663
|
-
e.preventDefault();
|
|
664
|
-
}
|
|
665
|
-
/**
|
|
666
|
-
* Handles left arrow key navigation for buttons.
|
|
687
|
+
* Returns the number of actual visible slides.
|
|
667
688
|
*/
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const playButton = this.playButton();
|
|
671
|
-
if (playButton) {
|
|
672
|
-
this.focusOnButton(playButton);
|
|
673
|
-
}
|
|
674
|
-
const pauseButton = this.pauseButton();
|
|
675
|
-
if (pauseButton) {
|
|
676
|
-
this.focusOnButton(pauseButton);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
setTimeout(() => {
|
|
681
|
-
this.goToPreviousSlide(false);
|
|
682
|
-
const button = this.navigationButtons().at(this.getCurrentSlideIndex());
|
|
683
|
-
this.focusOnButton(button);
|
|
684
|
-
}, this.TIMEOUT_INTERVAL);
|
|
685
|
-
e.preventDefault();
|
|
686
|
-
}
|
|
689
|
+
getSlidesCount() {
|
|
690
|
+
return this.slides()?.length ?? 0;
|
|
687
691
|
}
|
|
688
692
|
/**
|
|
689
693
|
* Observes screen breakpoint changes to adjust autoplay and layout behavior.
|
|
@@ -692,31 +696,31 @@ class EclCarouselComponent extends ECLBaseDirective {
|
|
|
692
696
|
this.cdkBreakpointObserver
|
|
693
697
|
.observe([
|
|
694
698
|
'(max-width: 768px)',
|
|
695
|
-
'(max-width: 997px)',
|
|
696
699
|
'(min-width: 998px)',
|
|
697
700
|
'(min-width: 1140px)',
|
|
698
701
|
'(min-width: 1140px) and (max-width: 1260px)',
|
|
699
702
|
])
|
|
700
703
|
.pipe(takeUntil(this.destroy$))
|
|
701
704
|
.subscribe((result) => {
|
|
702
|
-
this.isMobileBreakpoint
|
|
703
|
-
this.isSmallTabletBreakpoint = result.breakpoints['(max-width: 997px)'];
|
|
705
|
+
this.isMobileBreakpoint.set(result.breakpoints['(max-width: 768px)']);
|
|
704
706
|
this.isTabletBreakpoint = result.breakpoints['(min-width: 998px)'];
|
|
705
707
|
this.isLargeBreakpoint = result.breakpoints['(min-width: 1140px)'];
|
|
708
|
+
this.isDesktopBreakpointActive.set(result.breakpoints['(min-width: 1140px)']);
|
|
706
709
|
this.isDesktopBreakpoint = result.breakpoints['(min-width: 1140px) and (max-width: 1260px)'];
|
|
707
|
-
|
|
708
|
-
if
|
|
709
|
-
|
|
710
|
+
// Deactivate autoplay on mobile; (re)activate it on larger
|
|
711
|
+
// screens if autoplay is desired. Focus is never moved here so
|
|
712
|
+
// the page is not scrolled to the carousel on load/resize.
|
|
713
|
+
if (this.isMobileBreakpoint()) {
|
|
714
|
+
this.stopAutoplay();
|
|
715
|
+
return;
|
|
710
716
|
}
|
|
711
|
-
if (this.isTabletBreakpoint) {
|
|
712
|
-
|
|
713
|
-
this.startPlaying();
|
|
714
|
-
}
|
|
717
|
+
if (this.isTabletBreakpoint && this.isAutoplayDesired() && !this.isPausedForced()) {
|
|
718
|
+
this.onPlayClick(false);
|
|
715
719
|
}
|
|
716
720
|
});
|
|
717
721
|
}
|
|
718
722
|
/**
|
|
719
|
-
* Observes changes to the carousel's
|
|
723
|
+
* Observes changes to the carousel's viewport element size using a timeout debounce.
|
|
720
724
|
*/
|
|
721
725
|
observeElementSizeChanges() {
|
|
722
726
|
setTimeout(() => {
|
|
@@ -726,13 +730,13 @@ class EclCarouselComponent extends ECLBaseDirective {
|
|
|
726
730
|
else {
|
|
727
731
|
this.resetHeight();
|
|
728
732
|
}
|
|
733
|
+
this.embla()?.reInit();
|
|
729
734
|
}, this.TIMEOUT_INTERVAL);
|
|
730
735
|
}
|
|
731
736
|
/**
|
|
732
737
|
* Normalizes the height of all banners to match the tallest banner.
|
|
733
738
|
*/
|
|
734
739
|
checkBannerHeights() {
|
|
735
|
-
// First, let each banner calculate its own height (like ECL's bannerInstance.setHeight(ratio))
|
|
736
740
|
this.slides().forEach((slide) => {
|
|
737
741
|
const banner = slide.banner();
|
|
738
742
|
if (banner) {
|
|
@@ -773,7 +777,6 @@ class EclCarouselComponent extends ECLBaseDirective {
|
|
|
773
777
|
this.renderer2.setStyle(bannerVideo.el.nativeElement, 'aspect-ratio', 'auto');
|
|
774
778
|
}
|
|
775
779
|
});
|
|
776
|
-
this.resetHiddenItems();
|
|
777
780
|
}
|
|
778
781
|
}
|
|
779
782
|
/**
|
|
@@ -795,32 +798,27 @@ class EclCarouselComponent extends ECLBaseDirective {
|
|
|
795
798
|
}
|
|
796
799
|
}
|
|
797
800
|
});
|
|
798
|
-
this.resetHiddenItems();
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* Re-renders the content of hidden buffer items after style reset.
|
|
802
|
-
*/
|
|
803
|
-
resetHiddenItems() {
|
|
804
|
-
if (this.hasControls()) {
|
|
805
|
-
this.firstHiddenItem().el.nativeElement.innerHTML = this.getLastContentChild();
|
|
806
|
-
this.lastHiddenItem().el.nativeElement.innerHTML = this.getFirstContentChild();
|
|
807
|
-
}
|
|
808
801
|
}
|
|
809
802
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: EclCarouselComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
810
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: EclCarouselComponent, isStandalone: true, selector: "ecl-carousel", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, isAutoplay: { classPropertyName: "isAutoplay", publicName: "isAutoplay", isSignal: true, isRequired: false, transformFunction: null }, playInterval: { classPropertyName: "playInterval", publicName: "playInterval", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { slide: "slide" }, host: { properties: { "attr.aria-label": "ariaLabel()", "attr.aria-roledescription": "\"carousel\"", "class": "cssClasses()" } }, queries: [{ propertyName: "slides", predicate: i0.forwardRef(() => EclCarouselItemComponent), isSignal: true }], viewQueries: [{ propertyName: "carouselContainerElement", first: true, predicate: ["carouselContainer"], descendants: true, isSignal: true }, { propertyName: "
|
|
803
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: EclCarouselComponent, isStandalone: true, selector: "ecl-carousel", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, isAutoplay: { classPropertyName: "isAutoplay", publicName: "isAutoplay", isSignal: true, isRequired: false, transformFunction: null }, playInterval: { classPropertyName: "playInterval", publicName: "playInterval", isSignal: true, isRequired: false, transformFunction: null }, counterLabel: { classPropertyName: "counterLabel", publicName: "counterLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { slide: "slide" }, host: { properties: { "attr.aria-label": "ariaLabel()", "attr.aria-roledescription": "\"carousel\"", "class": "cssClasses()", "style.--ecl-carousel-slide-duration": "playInterval() + \"ms\"" } }, queries: [{ propertyName: "slides", predicate: i0.forwardRef(() => EclCarouselItemComponent), isSignal: true }], viewQueries: [{ propertyName: "carouselContainerElement", first: true, predicate: ["carouselContainer"], descendants: true, isSignal: true }, { propertyName: "embla", first: true, predicate: ["embla"], descendants: true, isSignal: true }, { propertyName: "teaserButtons", predicate: ["teaserButton"], descendants: true, isSignal: true }, { propertyName: "loadingBars", predicate: ["loadingBar"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (hasControls()) {\n<div class=\"ecl-carousel__controls\">\n <div class=\"ecl-container\">\n <div class=\"ecl-carousel__navigation\">\n <div class=\"ecl-carousel__pager-container\">\n <ecl-slider-pager\n class=\"ecl-carousel__pager\"\n [variant]=\"pagerVariant()\"\n size=\"m\"\n [isPlaying]=\"isPagerPlaying()\"\n [activeIndex]=\"getCurrentSlideIndex()\"\n [dotIndices]=\"getDotIndices()\"\n [dotAriaLabels]=\"getDotAriaLabels()\"\n prevExtraClasses=\"ecl-carousel__prev\"\n nextExtraClasses=\"ecl-carousel__next\"\n dotsExtraClasses=\"ecl-carousel__dots\"\n dotExtraClasses=\"ecl-carousel__dot\"\n playExtraClasses=\"ecl-carousel__play\"\n pauseExtraClasses=\"ecl-carousel__pause\"\n (play)=\"onPlayClick()\"\n (pause)=\"onPauseClick()\"\n (prev)=\"onPreviousSlideClick()\"\n (next)=\"onNextSlideClick()\"\n (goTo)=\"onDotClick($event)\">\n </ecl-slider-pager>\n <div class=\"ecl-carousel__counter\" dir=\"ltr\">\n {{ getCounterCurrent() }} {{ counterLabel() || ('ecl.common.OF' | translate) }} {{ getCounterTotal() }}\n </div>\n </div>\n\n <div class=\"ecl-carousel__teasers\" role=\"tablist\">\n @for (slide of slides(); let i = $index; track slide) {\n <button #teaserButton type=\"button\" class=\"ecl-carousel__teaser-button\" role=\"tab\"\n [attr.data-ecl-carousel-slide-index]=\"i\"\n [attr.aria-current]=\"i === getCurrentSlideIndex() ? true : null\"\n [attr.tabindex]=\"i === getCurrentSlideIndex() ? null : -1\"\n [style.display]=\"isTeaserVisible(i) ? null : 'none'\"\n [style.pointer-events]=\"isTeaserVisible(i) ? null : 'none'\"\n [style.order]=\"getTeaserOrder(i)\" (click)=\"onTeaserClick(i)\"\n (keydown)=\"onTeaserKeyboardPress($event, i)\">\n <span class=\"ecl-carousel__teaser-loading-bar\" aria-hidden=\"true\"\n [class.is-active]=\"(isPlaying() || isHoverPaused() || isPausedForced()) && i === getCurrentSlideIndex()\"\n [class.is-paused]=\"(isHoverPaused() || isPausedForced()) && i === getCurrentSlideIndex()\">\n <span #loadingBar class=\"ecl-carousel__teaser-loading-bar-completion\"></span>\n </span>\n <span class=\"ecl-carousel__teaser-content\">\n @if (slide.teaserText()) {\n <span class=\"ecl-carousel__teaser-title\">{{ slide.teaserText() }}</span>\n }\n </span>\n </button>\n }\n </div>\n </div>\n </div>\n</div>\n}\n<div #carouselContainer class=\"ecl-carousel__viewport\" [class.ecl-carousel-container--padded]=\"isPadded()\"\n emblaCarousel #embla=\"emblaCarousel\" [options]=\"emblaOptions\" [plugins]=\"emblaPlugins\"\n [subscribeToEvents]=\"emblaEvents\" (emblaChange)=\"onEmblaEvent($event)\"\n (mouseenter)=\"onViewportPause()\" (mouseleave)=\"onViewportResume()\"\n (focusin)=\"onViewportPause()\" (focusout)=\"onViewportResume()\">\n <div class=\"ecl-carousel__slides\">\n <ng-content></ng-content>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: EmblaCarouselDirective, selector: "[emblaCarousel]", inputs: ["options", "plugins", "subscribeToEvents", "eventsThrottleTime"], outputs: ["emblaChange"], exportAs: ["emblaCarousel"] }, { kind: "component", type: i1.EclSliderPagerComponent, selector: "ecl-slider-pager", inputs: ["size", "variant", "isPlaying", "isPrevDisabled", "isNextDisabled", "activeIndex", "dotIndices", "dotAriaLabels", "prevExtraClasses", "nextExtraClasses", "dotsExtraClasses", "dotExtraClasses", "playExtraClasses", "pauseExtraClasses"], outputs: ["prev", "next", "goTo", "play", "pause"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
|
|
811
804
|
}
|
|
812
805
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: EclCarouselComponent, decorators: [{
|
|
813
806
|
type: Component,
|
|
814
807
|
args: [{ selector: 'ecl-carousel', imports: [
|
|
815
808
|
TranslatePipe,
|
|
816
|
-
EclIconComponent,
|
|
817
809
|
EclCarouselItemComponent,
|
|
810
|
+
EmblaCarouselDirective,
|
|
811
|
+
...EUI_ECL_SLIDER,
|
|
818
812
|
], host: {
|
|
819
813
|
'[attr.aria-label]': 'ariaLabel()',
|
|
820
814
|
'[attr.aria-roledescription]': '"carousel"',
|
|
821
815
|
'[class]': 'cssClasses()',
|
|
822
|
-
|
|
823
|
-
|
|
816
|
+
// Drives the teaser loading-bar animation duration (see @eui/styles).
|
|
817
|
+
// It has no CSS fallback, so without it the progress animation never
|
|
818
|
+
// runs; it must match the autoplay delay.
|
|
819
|
+
'[style.--ecl-carousel-slide-duration]': 'playInterval() + "ms"',
|
|
820
|
+
}, template: "@if (hasControls()) {\n<div class=\"ecl-carousel__controls\">\n <div class=\"ecl-container\">\n <div class=\"ecl-carousel__navigation\">\n <div class=\"ecl-carousel__pager-container\">\n <ecl-slider-pager\n class=\"ecl-carousel__pager\"\n [variant]=\"pagerVariant()\"\n size=\"m\"\n [isPlaying]=\"isPagerPlaying()\"\n [activeIndex]=\"getCurrentSlideIndex()\"\n [dotIndices]=\"getDotIndices()\"\n [dotAriaLabels]=\"getDotAriaLabels()\"\n prevExtraClasses=\"ecl-carousel__prev\"\n nextExtraClasses=\"ecl-carousel__next\"\n dotsExtraClasses=\"ecl-carousel__dots\"\n dotExtraClasses=\"ecl-carousel__dot\"\n playExtraClasses=\"ecl-carousel__play\"\n pauseExtraClasses=\"ecl-carousel__pause\"\n (play)=\"onPlayClick()\"\n (pause)=\"onPauseClick()\"\n (prev)=\"onPreviousSlideClick()\"\n (next)=\"onNextSlideClick()\"\n (goTo)=\"onDotClick($event)\">\n </ecl-slider-pager>\n <div class=\"ecl-carousel__counter\" dir=\"ltr\">\n {{ getCounterCurrent() }} {{ counterLabel() || ('ecl.common.OF' | translate) }} {{ getCounterTotal() }}\n </div>\n </div>\n\n <div class=\"ecl-carousel__teasers\" role=\"tablist\">\n @for (slide of slides(); let i = $index; track slide) {\n <button #teaserButton type=\"button\" class=\"ecl-carousel__teaser-button\" role=\"tab\"\n [attr.data-ecl-carousel-slide-index]=\"i\"\n [attr.aria-current]=\"i === getCurrentSlideIndex() ? true : null\"\n [attr.tabindex]=\"i === getCurrentSlideIndex() ? null : -1\"\n [style.display]=\"isTeaserVisible(i) ? null : 'none'\"\n [style.pointer-events]=\"isTeaserVisible(i) ? null : 'none'\"\n [style.order]=\"getTeaserOrder(i)\" (click)=\"onTeaserClick(i)\"\n (keydown)=\"onTeaserKeyboardPress($event, i)\">\n <span class=\"ecl-carousel__teaser-loading-bar\" aria-hidden=\"true\"\n [class.is-active]=\"(isPlaying() || isHoverPaused() || isPausedForced()) && i === getCurrentSlideIndex()\"\n [class.is-paused]=\"(isHoverPaused() || isPausedForced()) && i === getCurrentSlideIndex()\">\n <span #loadingBar class=\"ecl-carousel__teaser-loading-bar-completion\"></span>\n </span>\n <span class=\"ecl-carousel__teaser-content\">\n @if (slide.teaserText()) {\n <span class=\"ecl-carousel__teaser-title\">{{ slide.teaserText() }}</span>\n }\n </span>\n </button>\n }\n </div>\n </div>\n </div>\n</div>\n}\n<div #carouselContainer class=\"ecl-carousel__viewport\" [class.ecl-carousel-container--padded]=\"isPadded()\"\n emblaCarousel #embla=\"emblaCarousel\" [options]=\"emblaOptions\" [plugins]=\"emblaPlugins\"\n [subscribeToEvents]=\"emblaEvents\" (emblaChange)=\"onEmblaEvent($event)\"\n (mouseenter)=\"onViewportPause()\" (mouseleave)=\"onViewportResume()\"\n (focusin)=\"onViewportPause()\" (focusout)=\"onViewportResume()\">\n <div class=\"ecl-carousel__slides\">\n <ng-content></ng-content>\n </div>\n</div>\n" }]
|
|
821
|
+
}], ctorParameters: () => [], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], isAutoplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "isAutoplay", required: false }] }], playInterval: [{ type: i0.Input, args: [{ isSignal: true, alias: "playInterval", required: false }] }], counterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "counterLabel", required: false }] }], slide: [{ type: i0.Output, args: ["slide"] }], carouselContainerElement: [{ type: i0.ViewChild, args: ['carouselContainer', { isSignal: true }] }], slides: [{ type: i0.ContentChildren, args: [forwardRef(() => EclCarouselItemComponent), { isSignal: true }] }], embla: [{ type: i0.ViewChild, args: ['embla', { isSignal: true }] }], teaserButtons: [{ type: i0.ViewChildren, args: ['teaserButton', { isSignal: true }] }], loadingBars: [{ type: i0.ViewChildren, args: ['loadingBar', { isSignal: true }] }] } });
|
|
824
822
|
|
|
825
823
|
const EUI_ECL_CAROUSEL = [
|
|
826
824
|
EclCarouselComponent,
|