@eui/ecl 22.0.0-alpha.57 → 22.0.0-alpha.58

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.
@@ -552,26 +552,14 @@
552
552
  },
553
553
  {
554
554
  "name": "EclCarouselItem",
555
- "id": "interface-EclCarouselItem-d61eba83b49a5a2ffbe2ce148f2be8fd3257d1ba194641ac793d3e3bcbda764bebfd3b8f84223cacd0ccb560fb3ce3dd3b4887b76832324f2a68bbe8b0a9818f",
555
+ "id": "interface-EclCarouselItem-8d955c58945f6787340cbd552b18fa685c60eeebf5b17f56e0dde36e36322b2f435747dc6781900b5e026e2aad4c74495cb82902739dd8a099754d6b88ee7591",
556
556
  "file": "packages/ecl/components/ecl-carousel/models/ecl-carousel-item.model.ts",
557
557
  "deprecated": false,
558
558
  "deprecationMessage": "",
559
559
  "type": "interface",
560
- "sourceCode": "import { Signal, WritableSignal } from '@angular/core';\n\n/**\n * Interface implemented by `EclCarouselItemComponent`.\n * Defines the minimal contract the carousel uses to manage slide state.\n */\nexport interface EclCarouselItem {\n\t/** Whether this slide is currently active (visible). */\n\tisCurrent: WritableSignal<boolean>;\n\t/** Zero-based index assigned by the parent carousel. */\n\tindex: WritableSignal<number | undefined>;\n\t/** Optional slide identifier used for navigation buttons. */\n\tid?: Signal<string | undefined>;\n}\n",
560
+ "sourceCode": "import { WritableSignal } from '@angular/core';\n\n/**\n * Interface implemented by `EclCarouselItemComponent`.\n * Defines the minimal contract the carousel uses to manage slide state.\n */\nexport interface EclCarouselItem {\n\t/** Whether this slide is currently active (visible). */\n\tisCurrent: WritableSignal<boolean>;\n\t/** Zero-based index assigned by the parent carousel. */\n\tindex: WritableSignal<number | undefined>;\n}\n",
561
561
  "displayName": "EclCarouselItem",
562
562
  "properties": [
563
- {
564
- "name": "id",
565
- "coverageIgnore": false,
566
- "deprecated": false,
567
- "deprecationMessage": "",
568
- "type": "Signal<string | undefined>",
569
- "indexKey": "",
570
- "optional": true,
571
- "description": "<p>Optional slide identifier used for navigation buttons.</p>\n",
572
- "line": 13,
573
- "rawdescription": "\nOptional slide identifier used for navigation buttons."
574
- },
575
563
  {
576
564
  "name": "index",
577
565
  "coverageIgnore": false,
@@ -53638,7 +53626,7 @@
53638
53626
  },
53639
53627
  {
53640
53628
  "name": "EclCarouselComponent",
53641
- "id": "component-EclCarouselComponent-b957608038744440508152a9d561865145a0261afd68ff93803e9af36f5cb92f22b41a3cbd144f6a2841b50dee3db84a82474621bc24f0f8c465b69a39627855",
53629
+ "id": "component-EclCarouselComponent-e22f198b6fa2a6c718451d77e8b1243d7090130042b82a12881ebbc3d1543c0952e910a3e56a286a0c8eabd92cb28637219e0f56c012e7fd99565e0ba3677280",
53642
53630
  "file": "packages/ecl/components/ecl-carousel/ecl-carousel.component.ts",
53643
53631
  "encapsulation": [],
53644
53632
  "entryComponents": [],
@@ -53666,30 +53654,13 @@
53666
53654
  "indexKey": "",
53667
53655
  "optional": false,
53668
53656
  "description": "<p>Aria-label for screen readers to describe the carousel.</p>\n",
53669
- "line": 83,
53657
+ "line": 82,
53670
53658
  "rawdescription": "\n\nAria-label for screen readers to describe the carousel.\n",
53671
53659
  "modifierKind": [
53672
53660
  148
53673
53661
  ],
53674
53662
  "required": false
53675
53663
  },
53676
- {
53677
- "name": "counterLabel",
53678
- "coverageIgnore": false,
53679
- "defaultValue": "''",
53680
- "deprecated": false,
53681
- "deprecationMessage": "",
53682
- "type": "string",
53683
- "indexKey": "",
53684
- "optional": false,
53685
- "description": "<p>Separator label used by the counter, e.g. the &quot;of&quot; in &quot;1 of 5&quot;.\nDefaults to the translated <code>ecl.common.OF</code> key.</p>\n",
53686
- "line": 99,
53687
- "rawdescription": "\n\nSeparator label used by the counter, e.g. the \"of\" in \"1 of 5\".\nDefaults to the translated `ecl.common.OF` key.\n",
53688
- "modifierKind": [
53689
- 148
53690
- ],
53691
- "required": false
53692
- },
53693
53664
  {
53694
53665
  "name": "isAutoplay",
53695
53666
  "coverageIgnore": false,
@@ -53700,7 +53671,7 @@
53700
53671
  "indexKey": "",
53701
53672
  "optional": false,
53702
53673
  "description": "<p>Determines whether the carousel should autoplay.</p>\n",
53703
- "line": 88,
53674
+ "line": 87,
53704
53675
  "rawdescription": "\n\nDetermines whether the carousel should autoplay.\n",
53705
53676
  "modifierKind": [
53706
53677
  148
@@ -53717,7 +53688,7 @@
53717
53688
  "indexKey": "",
53718
53689
  "optional": false,
53719
53690
  "description": "<p>Interval in milliseconds between automatic slide transitions.</p>\n",
53720
- "line": 93,
53691
+ "line": 92,
53721
53692
  "rawdescription": "\n\nInterval in milliseconds between automatic slide transitions.\n",
53722
53693
  "modifierKind": [
53723
53694
  148
@@ -53793,7 +53764,7 @@
53793
53764
  "indexKey": "",
53794
53765
  "optional": false,
53795
53766
  "description": "<p>Event emitted when a slide change occurs.</p>\n",
53796
- "line": 104,
53767
+ "line": 97,
53797
53768
  "rawdescription": "\n\nEvent emitted when a slide change occurs.\n",
53798
53769
  "modifierKind": [
53799
53770
  148
@@ -53832,7 +53803,7 @@
53832
53803
  "indexKey": "",
53833
53804
  "optional": false,
53834
53805
  "description": "<p>Reference to the main carousel viewport element (the Embla root).</p>\n",
53835
- "line": 109,
53806
+ "line": 102,
53836
53807
  "rawdescription": "\n\nReference to the main carousel viewport element (the Embla root).\n",
53837
53808
  "modifierKind": [
53838
53809
  148
@@ -53848,7 +53819,7 @@
53848
53819
  "indexKey": "",
53849
53820
  "optional": false,
53850
53821
  "description": "<p>Returns the computed CSS class list for the component.</p>\n",
53851
- "line": 230,
53822
+ "line": 223,
53852
53823
  "rawdescription": "\n\nReturns the computed CSS class list for the component.\n",
53853
53824
  "modifierKind": [
53854
53825
  124,
@@ -53865,7 +53836,7 @@
53865
53836
  "indexKey": "",
53866
53837
  "optional": false,
53867
53838
  "description": "<p>Embla events the component reacts to.</p>\n",
53868
- "line": 182,
53839
+ "line": 175,
53869
53840
  "rawdescription": "\n\nEmbla events the component reacts to.\n",
53870
53841
  "modifierKind": [
53871
53842
  124,
@@ -53882,7 +53853,7 @@
53882
53853
  "indexKey": "",
53883
53854
  "optional": false,
53884
53855
  "description": "<p>Embla options, mirroring the upstream ECL configuration.</p>\n",
53885
- "line": 168,
53856
+ "line": 161,
53886
53857
  "rawdescription": "\n\nEmbla options, mirroring the upstream ECL configuration.\n",
53887
53858
  "modifierKind": [
53888
53859
  124
@@ -53898,7 +53869,7 @@
53898
53869
  "indexKey": "",
53899
53870
  "optional": false,
53900
53871
  "description": "<p>Embla plugins. Built once so the Embla directive does not needlessly re-init.</p>\n",
53901
- "line": 177,
53872
+ "line": 170,
53902
53873
  "rawdescription": "\n\nEmbla plugins. Built once so the Embla directive does not needlessly re-init.\n",
53903
53874
  "modifierKind": [
53904
53875
  124
@@ -53914,7 +53885,7 @@
53914
53885
  "indexKey": "",
53915
53886
  "optional": false,
53916
53887
  "description": "<p>Whether the viewport is at or above the ECL desktop breakpoint (1140px).\nThis is the boundary the ECL CSS uses to show the play/pause controls and\nthe teaser tablist (and to hide the mobile dots). A signal so the pager\nvariant can react: play/pause only renders at/above this width, matching\nthe CSS so the pause button never leaks onto narrower viewports.</p>\n",
53917
- "line": 163,
53888
+ "line": 156,
53918
53889
  "rawdescription": "\n\nWhether the viewport is at or above the ECL desktop breakpoint (1140px).\nThis is the boundary the ECL CSS uses to show the play/pause controls and\nthe teaser tablist (and to hide the mobile dots). A signal so the pager\nvariant can react: play/pause only renders at/above this width, matching\nthe CSS so the pause button never leaks onto narrower viewports.\n",
53919
53890
  "modifierKind": [
53920
53891
  124,
@@ -53931,7 +53902,7 @@
53931
53902
  "indexKey": "",
53932
53903
  "optional": false,
53933
53904
  "description": "<p>Indicates whether autoplay is temporarily paused by hover/focus. Unlike a\nmanual pause, the loading bar stays visible and frozen at its current\nprogress (matching ECL), resuming when the pointer/focus leaves.</p>\n",
53934
- "line": 147,
53905
+ "line": 140,
53935
53906
  "rawdescription": "\n\nIndicates whether autoplay is temporarily paused by hover/focus. Unlike a\nmanual pause, the loading bar stays visible and frozen at its current\nprogress (matching ECL), resuming when the pointer/focus leaves.\n",
53936
53907
  "modifierKind": [
53937
53908
  124,
@@ -53948,7 +53919,7 @@
53948
53919
  "indexKey": "",
53949
53920
  "optional": false,
53950
53921
  "description": "<p>Whether the current viewport is at the mobile breakpoint. A signal so the\ntemplate can react (the pager drops play/pause on mobile, matching ECL,\nwhere play/pause is a desktop-only control).</p>\n",
53951
- "line": 154,
53922
+ "line": 147,
53952
53923
  "rawdescription": "\n\nWhether the current viewport is at the mobile breakpoint. A signal so the\ntemplate can react (the pager drops play/pause on mobile, matching ECL,\nwhere play/pause is a desktop-only control).\n",
53953
53924
  "modifierKind": [
53954
53925
  124,
@@ -53965,7 +53936,7 @@
53965
53936
  "indexKey": "",
53966
53937
  "optional": false,
53967
53938
  "description": "<p>Indicates whether autoplay was manually paused (via the pause button or\non mobile). When paused this way the loading bar is cleared.</p>\n",
53968
- "line": 125,
53939
+ "line": 118,
53969
53940
  "rawdescription": "\n\nIndicates whether autoplay was manually paused (via the pause button or\non mobile). When paused this way the loading bar is cleared.\n",
53970
53941
  "modifierKind": [
53971
53942
  148
@@ -53981,7 +53952,7 @@
53981
53952
  "indexKey": "",
53982
53953
  "optional": false,
53983
53954
  "description": "<p>Indicates whether autoplay is currently active.</p>\n",
53984
- "line": 119,
53955
+ "line": 112,
53985
53956
  "rawdescription": "\n\nIndicates whether autoplay is currently active.\n",
53986
53957
  "modifierKind": [
53987
53958
  148
@@ -53997,7 +53968,7 @@
53997
53968
  "indexKey": "",
53998
53969
  "optional": false,
53999
53970
  "description": "<p>Indicates whether the layout is currently in RTL mode.</p>\n",
54000
- "line": 130,
53971
+ "line": 123,
54001
53972
  "rawdescription": "\n\nIndicates whether the layout is currently in RTL mode.\n"
54002
53973
  },
54003
53974
  {
@@ -54010,7 +53981,7 @@
54010
53981
  "indexKey": "",
54011
53982
  "optional": false,
54012
53983
  "description": "<p>Collection of slide components rendered inside the carousel.</p>\n",
54013
- "line": 114,
53984
+ "line": 107,
54014
53985
  "rawdescription": "\n\nCollection of slide components rendered inside the carousel.\n",
54015
53986
  "modifierKind": [
54016
53987
  148
@@ -54025,7 +53996,7 @@
54025
53996
  "optional": false,
54026
53997
  "returnType": "number",
54027
53998
  "typeParameters": [],
54028
- "line": 316,
53999
+ "line": 309,
54029
54000
  "deprecated": false,
54030
54001
  "deprecationMessage": "",
54031
54002
  "rawdescription": "\n\nReturns the 1-based position of the active slide (for the counter).\n",
@@ -54038,7 +54009,7 @@
54038
54009
  "optional": false,
54039
54010
  "returnType": "number",
54040
54011
  "typeParameters": [],
54041
- "line": 323,
54012
+ "line": 316,
54042
54013
  "deprecated": false,
54043
54014
  "deprecationMessage": "",
54044
54015
  "rawdescription": "\n\nReturns the total number of slides (for the counter).\n",
@@ -54051,7 +54022,7 @@
54051
54022
  "optional": false,
54052
54023
  "returnType": "number",
54053
54024
  "typeParameters": [],
54054
- "line": 309,
54025
+ "line": 302,
54055
54026
  "deprecated": false,
54056
54027
  "deprecationMessage": "",
54057
54028
  "rawdescription": "\n\nRetrieves the current active slide index (zero-based).\n",
@@ -54064,7 +54035,7 @@
54064
54035
  "optional": false,
54065
54036
  "returnType": "string[]",
54066
54037
  "typeParameters": [],
54067
- "line": 340,
54038
+ "line": 333,
54068
54039
  "deprecated": false,
54069
54040
  "deprecationMessage": "",
54070
54041
  "rawdescription": "\n\nPre-translated accessible label for each pager dot. Translated here since\nthe shared `ecl-slider-pager` receives plain strings.\n",
@@ -54077,7 +54048,7 @@
54077
54048
  "optional": false,
54078
54049
  "returnType": "number[]",
54079
54050
  "typeParameters": [],
54080
- "line": 332,
54051
+ "line": 325,
54081
54052
  "deprecated": false,
54082
54053
  "deprecationMessage": "",
54083
54054
  "rawdescription": "\n\nDot indices for the mobile slider pager (one dot per slide). The dots are\nhidden on desktop via the ECL CSS (`.ecl-carousel__dots { display: none }`)\nand shown at the mobile breakpoint.\n",
@@ -54099,7 +54070,7 @@
54099
54070
  "optional": false,
54100
54071
  "returnType": "number",
54101
54072
  "typeParameters": [],
54102
- "line": 365,
54073
+ "line": 358,
54103
54074
  "deprecated": false,
54104
54075
  "deprecationMessage": "",
54105
54076
  "rawdescription": "\n\nReturns the flex order of a teaser button within the visible window.\n",
@@ -54125,7 +54096,7 @@
54125
54096
  "optional": false,
54126
54097
  "returnType": "boolean",
54127
54098
  "typeParameters": [],
54128
- "line": 302,
54099
+ "line": 295,
54129
54100
  "deprecated": false,
54130
54101
  "deprecationMessage": "",
54131
54102
  "rawdescription": "\n\nChecks whether there are enough slides to render controls.\n",
@@ -54138,7 +54109,7 @@
54138
54109
  "optional": false,
54139
54110
  "returnType": "boolean",
54140
54111
  "typeParameters": [],
54141
- "line": 295,
54112
+ "line": 288,
54142
54113
  "deprecated": false,
54143
54114
  "deprecationMessage": "",
54144
54115
  "rawdescription": "\n\nReturns whether the carousel has padding applied (e.g., on desktop breakpoint).\n",
@@ -54151,7 +54122,7 @@
54151
54122
  "optional": false,
54152
54123
  "returnType": "boolean",
54153
54124
  "typeParameters": [],
54154
- "line": 377,
54125
+ "line": 370,
54155
54126
  "deprecated": false,
54156
54127
  "deprecationMessage": "",
54157
54128
  "rawdescription": "\n\nWhether the pager should show the \"pause\" control (i.e. autoplay is\nactively running). While the pointer/focus hover-pauses the carousel the\ntimer is suspended, so the pager must show \"play\" even though a manual\npause was not triggered. Mirrors upstream ECL, where hovering flips the\ncontrol to play.\n",
@@ -54176,7 +54147,7 @@
54176
54147
  "optional": false,
54177
54148
  "returnType": "boolean",
54178
54149
  "typeParameters": [],
54179
- "line": 353,
54150
+ "line": 346,
54180
54151
  "deprecated": false,
54181
54152
  "deprecationMessage": "",
54182
54153
  "rawdescription": "\n\nReturns whether a teaser button should be visible in the current window.\n",
@@ -54202,7 +54173,7 @@
54202
54173
  "optional": false,
54203
54174
  "returnType": "void",
54204
54175
  "typeParameters": [],
54205
- "line": 278,
54176
+ "line": 271,
54206
54177
  "deprecated": false,
54207
54178
  "deprecationMessage": "",
54208
54179
  "rawdescription": "\n\nAngular lifecycle hook: called after content (slides) is initialized.\n",
@@ -54215,7 +54186,7 @@
54215
54186
  "optional": false,
54216
54187
  "returnType": "void",
54217
54188
  "typeParameters": [],
54218
- "line": 287,
54189
+ "line": 280,
54219
54190
  "deprecated": false,
54220
54191
  "deprecationMessage": "",
54221
54192
  "rawdescription": "\n\nAngular lifecycle hook: cleans up subscriptions.\n",
@@ -54228,7 +54199,7 @@
54228
54199
  "optional": false,
54229
54200
  "returnType": "void",
54230
54201
  "typeParameters": [],
54231
- "line": 251,
54202
+ "line": 244,
54232
54203
  "deprecated": false,
54233
54204
  "deprecationMessage": "",
54234
54205
  "rawdescription": "\n\nAngular lifecycle hook: initializes RTL detection and the Embla plugins.\n",
@@ -54250,7 +54221,7 @@
54250
54221
  "optional": false,
54251
54222
  "returnType": "void",
54252
54223
  "typeParameters": [],
54253
- "line": 481,
54224
+ "line": 474,
54254
54225
  "deprecated": false,
54255
54226
  "deprecationMessage": "",
54256
54227
  "rawdescription": "\n\nHandles a dot activation from the mobile slider pager: scrolls to the\nmatching slide and stops autoplay (same behavior as a teaser click).\n",
@@ -54288,7 +54259,7 @@
54288
54259
  "optional": false,
54289
54260
  "returnType": "void",
54290
54261
  "typeParameters": [],
54291
- "line": 400,
54262
+ "line": 393,
54292
54263
  "deprecated": false,
54293
54264
  "deprecationMessage": "",
54294
54265
  "rawdescription": "\n\nHandles the throttled Embla events the component subscribed to.\n",
@@ -54317,7 +54288,7 @@
54317
54288
  "optional": false,
54318
54289
  "returnType": "void",
54319
54290
  "typeParameters": [],
54320
- "line": 456,
54291
+ "line": 449,
54321
54292
  "deprecated": false,
54322
54293
  "deprecationMessage": "",
54323
54294
  "rawdescription": "\n\nNavigates to the next slide and stops autoplay.\n",
@@ -54333,7 +54304,7 @@
54333
54304
  "optional": false,
54334
54305
  "returnType": "void",
54335
54306
  "typeParameters": [],
54336
- "line": 437,
54307
+ "line": 430,
54337
54308
  "deprecated": false,
54338
54309
  "deprecationMessage": "",
54339
54310
  "rawdescription": "\n\nStops autoplay and focuses the play button.\n",
@@ -54359,7 +54330,7 @@
54359
54330
  "optional": false,
54360
54331
  "returnType": "void",
54361
54332
  "typeParameters": [],
54362
- "line": 423,
54333
+ "line": 416,
54363
54334
  "deprecated": false,
54364
54335
  "deprecationMessage": "",
54365
54336
  "rawdescription": "\n\nStarts autoplay.\n\n button. Auto-starts (e.g. on breakpoint changes) pass false so the\n page is not scrolled to the carousel on load.\n",
@@ -54370,8 +54341,8 @@
54370
54341
  "jsdoctags": [
54371
54342
  {
54372
54343
  "name": {
54373
- "pos": 14264,
54374
- "end": 14273,
54344
+ "pos": 13986,
54345
+ "end": 13995,
54375
54346
  "kind": 80,
54376
54347
  "id": 0,
54377
54348
  "flags": 16842752,
@@ -54385,8 +54356,8 @@
54385
54356
  "deprecationMessage": "",
54386
54357
  "defaultValue": "true",
54387
54358
  "tagName": {
54388
- "pos": 14258,
54389
- "end": 14263,
54359
+ "pos": 13980,
54360
+ "end": 13985,
54390
54361
  "kind": 80,
54391
54362
  "id": 0,
54392
54363
  "flags": 16842752,
@@ -54404,7 +54375,7 @@
54404
54375
  "optional": false,
54405
54376
  "returnType": "void",
54406
54377
  "typeParameters": [],
54407
- "line": 464,
54378
+ "line": 457,
54408
54379
  "deprecated": false,
54409
54380
  "deprecationMessage": "",
54410
54381
  "rawdescription": "\n\nNavigates to the previous slide and stops autoplay.\n",
@@ -54429,7 +54400,7 @@
54429
54400
  "optional": false,
54430
54401
  "returnType": "void",
54431
54402
  "typeParameters": [],
54432
- "line": 472,
54403
+ "line": 465,
54433
54404
  "deprecated": false,
54434
54405
  "deprecationMessage": "",
54435
54406
  "rawdescription": "\n\nHandles user interaction with a teaser button: activates the matching slide.\n",
@@ -54475,7 +54446,7 @@
54475
54446
  "optional": false,
54476
54447
  "returnType": "void",
54477
54448
  "typeParameters": [],
54478
- "line": 489,
54449
+ "line": 482,
54479
54450
  "deprecated": false,
54480
54451
  "deprecationMessage": "",
54481
54452
  "rawdescription": "\n\nHandles keyboard navigation (ArrowLeft/ArrowRight) across teaser buttons.\n",
@@ -54515,7 +54486,7 @@
54515
54486
  "optional": false,
54516
54487
  "returnType": "void",
54517
54488
  "typeParameters": [],
54518
- "line": 507,
54489
+ "line": 500,
54519
54490
  "deprecated": false,
54520
54491
  "deprecationMessage": "",
54521
54492
  "rawdescription": "\n\nFreezes the loading bar while the pointer/focus is over the carousel.\nThe Embla autoplay plugin pauses the timer itself (stopOnMouseEnter /\nstopOnFocusIn); this only mirrors that as a visible paused state.\n",
@@ -54531,7 +54502,7 @@
54531
54502
  "optional": false,
54532
54503
  "returnType": "void",
54533
54504
  "typeParameters": [],
54534
- "line": 517,
54505
+ "line": 510,
54535
54506
  "deprecated": false,
54536
54507
  "deprecationMessage": "",
54537
54508
  "rawdescription": "\n\nResumes the loading bar once the pointer/focus leaves the carousel.\n",
@@ -54547,7 +54518,7 @@
54547
54518
  "optional": false,
54548
54519
  "returnType": "\"dots\" | \"play-pause-dots\"",
54549
54520
  "typeParameters": [],
54550
- "line": 393,
54521
+ "line": 386,
54551
54522
  "deprecated": false,
54552
54523
  "deprecationMessage": "",
54553
54524
  "rawdescription": "\n\nPager variant by breakpoint. Below the ECL desktop breakpoint (1140px)\nthe carousel shows only the dots navigation (prev / dots / next);\nplay/pause is a desktop-only control in ECL. Rendering `'dots'` there\navoids the pager's inline play/pause `display` style (which would\notherwise beat the ECL CSS and leave the pause button visible on narrower\nviewports). At/above 1140px the dots are hidden by CSS, so\n`'play-pause-dots'` yields play/pause + prev/next + teasers.\n\nThe 1140px boundary mirrors the media query the ECL CSS uses to toggle\n`.ecl-carousel__play/__pause` and `.ecl-carousel__teasers`.\n",
@@ -54641,23 +54612,23 @@
54641
54612
  "name": "EUI_ECL_SLIDER"
54642
54613
  }
54643
54614
  ],
54644
- "description": "<p>Carousel (slideshow) component following the ECL design system.\nRenders a sequence of <code>EclCarouselItemComponent</code> slides with autoplay, keyboard navigation,\nresponsive breakpoint awareness, and RTL layout support.</p>\n<p>The slide engine is powered by Embla (via <code>embla-carousel-angular</code>), matching the upstream\nECL implementation: an infinite <code>loop</code>, the <code>Autoplay</code> plugin driving the teaser loading bars,\nand a slider pager with previous/next/play/pause controls, a counter, and a tablist of teaser\nbuttons (one per slide).</p>\n<ul>\n<li>Selector: <code>ecl-carousel</code>.</li>\n<li>Project <code>ecl-carousel-item</code> elements as slides (also via <code>contentChildren</code>).</li>\n<li><code>ariaLabel</code> — ARIA label for the carousel landmark; aliased as <code>aria-label</code>.</li>\n<li><code>isAutoplay</code> — enables automatic slide advancement; boolean attribute.</li>\n<li><code>playInterval</code> — milliseconds between automatic transitions; default <code>5000</code>.</li>\n<li><code>counterLabel</code> — separator label used by the counter, defaults to <code>ecl.common.OF</code>.</li>\n<li><code>slide</code> — emits <code>EclSlideEvent</code> when the active slide changes.</li>\n</ul>\n",
54645
- "rawdescription": "\n\nCarousel (slideshow) component following the ECL design system.\nRenders a sequence of `EclCarouselItemComponent` slides with autoplay, keyboard navigation,\nresponsive breakpoint awareness, and RTL layout support.\n\nThe slide engine is powered by Embla (via `embla-carousel-angular`), matching the upstream\nECL implementation: an infinite `loop`, the `Autoplay` plugin driving the teaser loading bars,\nand a slider pager with previous/next/play/pause controls, a counter, and a tablist of teaser\nbuttons (one per slide).\n\n- Selector: `ecl-carousel`.\n- Project `ecl-carousel-item` elements as slides (also via `contentChildren`).\n- `ariaLabel` — ARIA label for the carousel landmark; aliased as `aria-label`.\n- `isAutoplay` — enables automatic slide advancement; boolean attribute.\n- `playInterval` — milliseconds between automatic transitions; default `5000`.\n- `counterLabel` — separator label used by the counter, defaults to `ecl.common.OF`.\n- `slide` — emits `EclSlideEvent` when the active slide changes.\n",
54615
+ "description": "<p>Carousel (slideshow) component following the ECL design system.\nRenders a sequence of <code>EclCarouselItemComponent</code> slides with autoplay, keyboard navigation,\nresponsive breakpoint awareness, and RTL layout support.</p>\n<p>The slide engine is powered by Embla (via <code>embla-carousel-angular</code>), matching the upstream\nECL implementation: an infinite <code>loop</code>, the <code>Autoplay</code> plugin driving the teaser loading bars,\nand a slider pager with previous/next/play/pause controls, a counter, and a tablist of teaser\nbuttons (one per slide).</p>\n<ul>\n<li>Selector: <code>ecl-carousel</code>.</li>\n<li>Project <code>ecl-carousel-item</code> elements as slides (also via <code>contentChildren</code>).</li>\n<li><code>ariaLabel</code> — ARIA label for the carousel landmark; aliased as <code>aria-label</code>.</li>\n<li><code>isAutoplay</code> — enables automatic slide advancement; boolean attribute.</li>\n<li><code>playInterval</code> — milliseconds between automatic transitions; default <code>5000</code>.</li>\n<li><code>slide</code> — emits <code>EclSlideEvent</code> when the active slide changes.</li>\n</ul>\n",
54616
+ "rawdescription": "\n\nCarousel (slideshow) component following the ECL design system.\nRenders a sequence of `EclCarouselItemComponent` slides with autoplay, keyboard navigation,\nresponsive breakpoint awareness, and RTL layout support.\n\nThe slide engine is powered by Embla (via `embla-carousel-angular`), matching the upstream\nECL implementation: an infinite `loop`, the `Autoplay` plugin driving the teaser loading bars,\nand a slider pager with previous/next/play/pause controls, a counter, and a tablist of teaser\nbuttons (one per slide).\n\n- Selector: `ecl-carousel`.\n- Project `ecl-carousel-item` elements as slides (also via `contentChildren`).\n- `ariaLabel` — ARIA label for the carousel landmark; aliased as `aria-label`.\n- `isAutoplay` — enables automatic slide advancement; boolean attribute.\n- `playInterval` — milliseconds between automatic transitions; default `5000`.\n- `slide` — emits `EclSlideEvent` when the active slide changes.\n",
54646
54617
  "type": "component",
54647
- "sourceCode": "import { BreakpointObserver } from '@angular/cdk/layout';\nimport {\n AfterContentInit,\n ChangeDetectorRef,\n Component,\n DOCUMENT,\n ElementRef,\n OnDestroy,\n OnInit,\n Renderer2,\n afterNextRender,\n booleanAttribute,\n computed,\n contentChildren,\n forwardRef,\n inject,\n input,\n numberAttribute,\n output,\n signal,\n viewChild,\n viewChildren,\n} from '@angular/core';\nimport { EUI_ECL_SLIDER } from '@eui/ecl/components/ecl-slider';\nimport { ECLBaseDirective, EclRtlService, KeyCode } from '@eui/ecl/core';\nimport { TranslatePipe, TranslateService } from '@ngx-translate/core';\nimport {\n EmblaEventType,\n EmblaOptionsType,\n EmblaPluginType,\n} from 'embla-carousel';\nimport { EmblaCarouselDirective } from 'embla-carousel-angular';\nimport Autoplay from 'embla-carousel-autoplay';\nimport { Subject, auditTime, fromEvent, takeUntil } from 'rxjs';\nimport { EclCarouselItemComponent } from './ecl-carousel-item.component';\nimport { EclSlideEvent } from './events/ecl-slide.event';\n\n/**\n * Number of teaser buttons visible at once on the desktop breakpoint.\n */\nconst VISIBLE_TEASERS = 4;\n\n/**\n * Carousel (slideshow) component following the ECL design system.\n * Renders a sequence of `EclCarouselItemComponent` slides with autoplay, keyboard navigation,\n * responsive breakpoint awareness, and RTL layout support.\n *\n * The slide engine is powered by Embla (via `embla-carousel-angular`), matching the upstream\n * ECL implementation: an infinite `loop`, the `Autoplay` plugin driving the teaser loading bars,\n * and a slider pager with previous/next/play/pause controls, a counter, and a tablist of teaser\n * buttons (one per slide).\n *\n * - Selector: `ecl-carousel`.\n * - Project `ecl-carousel-item` elements as slides (also via `contentChildren`).\n * - `ariaLabel` — ARIA label for the carousel landmark; aliased as `aria-label`.\n * - `isAutoplay` — enables automatic slide advancement; boolean attribute.\n * - `playInterval` — milliseconds between automatic transitions; default `5000`.\n * - `counterLabel` — separator label used by the counter, defaults to `ecl.common.OF`.\n * - `slide` — emits `EclSlideEvent` when the active slide changes.\n */\n@Component({\n selector: 'ecl-carousel',\n templateUrl: './ecl-carousel.component.html',\n imports: [\n TranslatePipe,\n EmblaCarouselDirective,\n ...EUI_ECL_SLIDER,\n ],\n host: {\n '[attr.aria-label]': 'ariaLabel()',\n '[attr.aria-roledescription]': '\"carousel\"',\n '[class]': 'cssClasses()',\n // Drives the teaser loading-bar animation duration (see @eui/styles).\n // It has no CSS fallback, so without it the progress animation never\n // runs; it must match the autoplay delay.\n '[style.--ecl-carousel-slide-duration]': 'playInterval() + \"ms\"',\n },\n})\nexport class EclCarouselComponent extends ECLBaseDirective implements OnInit, OnDestroy, AfterContentInit {\n /**\n * Aria-label for screen readers to describe the carousel.\n */\n readonly ariaLabel = input<string | null>('Carousel description here', { alias: 'aria-label' });\n\n /**\n * Determines whether the carousel should autoplay.\n */\n readonly isAutoplay = input<boolean, unknown>(undefined, { transform: booleanAttribute });\n\n /**\n * Interval in milliseconds between automatic slide transitions.\n */\n readonly playInterval = input(5000, { transform: numberAttribute });\n\n /**\n * Separator label used by the counter, e.g. the \"of\" in \"1 of 5\".\n * Defaults to the translated `ecl.common.OF` key.\n */\n readonly counterLabel = input<string>('');\n\n /**\n * Event emitted when a slide change occurs.\n */\n readonly slide = output<EclSlideEvent>();\n\n /**\n * Reference to the main carousel viewport element (the Embla root).\n */\n readonly carouselContainerElement = viewChild<ElementRef>('carouselContainer');\n\n /**\n * Collection of slide components rendered inside the carousel.\n */\n readonly slides = contentChildren(forwardRef(() => EclCarouselItemComponent));\n\n /**\n * Indicates whether autoplay is currently active.\n */\n readonly isPlaying = signal(false);\n\n /**\n * Indicates whether autoplay was manually paused (via the pause button or\n * on mobile). When paused this way the loading bar is cleared.\n */\n readonly isPausedForced = signal(false);\n\n /**\n * Indicates whether the layout is currently in RTL mode.\n */\n isRtl = false;\n\n /**\n * Zero-based index of the currently active slide (mirrors Embla's selected snap).\n */\n private activeIndex = 0;\n\n /**\n * Start index of the currently visible teaser window (desktop only).\n */\n private teaserWindowStart = 0;\n\n /**\n * Indicates whether autoplay is temporarily paused by hover/focus. Unlike a\n * manual pause, the loading bar stays visible and frozen at its current\n * progress (matching ECL), resuming when the pointer/focus leaves.\n */\n protected readonly isHoverPaused = signal(false);\n\n /**\n * Whether the current viewport is at the mobile breakpoint. A signal so the\n * template can react (the pager drops play/pause on mobile, matching ECL,\n * where play/pause is a desktop-only control).\n */\n protected readonly isMobileBreakpoint = signal(false);\n\n /**\n * Whether the viewport is at or above the ECL desktop breakpoint (1140px).\n * This is the boundary the ECL CSS uses to show the play/pause controls and\n * the teaser tablist (and to hide the mobile dots). A signal so the pager\n * variant can react: play/pause only renders at/above this width, matching\n * the CSS so the pause button never leaks onto narrower viewports.\n */\n protected readonly isDesktopBreakpointActive = signal(false);\n\n /**\n * Embla options, mirroring the upstream ECL configuration.\n */\n protected emblaOptions: EmblaOptionsType = {\n loop: true,\n align: 'start',\n duration: 20,\n };\n\n /**\n * Embla plugins. Built once so the Embla directive does not needlessly re-init.\n */\n protected emblaPlugins: EmblaPluginType[] = [];\n\n /**\n * Embla events the component reacts to.\n */\n protected readonly emblaEvents: EmblaEventType[] = [\n 'select',\n 'reInit',\n 'autoplay:timerset',\n ];\n\n /**\n * Reference to the Embla directive driving the slide engine.\n */\n private readonly embla = viewChild<EmblaCarouselDirective>('embla');\n\n /**\n * References to the teaser navigation buttons corresponding to each slide.\n */\n private readonly teaserButtons = viewChildren<ElementRef>('teaserButton');\n\n /**\n * References to the teaser loading-bar completion elements (one per slide),\n * used to restart the progress animation when the active slide changes.\n */\n private readonly loadingBars = viewChildren<ElementRef>('loadingBar');\n\n private isTabletBreakpoint = false;\n private isLargeBreakpoint = false;\n private isDesktopBreakpoint = false;\n\n private destroy$: Subject<boolean> = new Subject<boolean>();\n\n /**\n * Debounce interval (ms) used internally for timing and transitions.\n */\n private readonly TIMEOUT_INTERVAL = 400;\n private readonly cdkBreakpointObserver = inject(BreakpointObserver);\n private readonly cd = inject(ChangeDetectorRef);\n private readonly renderer2 = inject(Renderer2);\n private readonly eclRtlService = inject(EclRtlService);\n private readonly translate = inject(TranslateService);\n private readonly document = inject<Document>(DOCUMENT);\n private readonly window = this.document.defaultView;\n /**\n * Host element, used to locate the slider pager's play/pause buttons by their\n * ECL classes for focus management after a play/pause toggle.\n */\n private readonly hostEl = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /**\n * Returns the computed CSS class list for the component.\n */\n protected readonly cssClasses = computed<string>(() =>\n super.getCssClasses('ecl-carousel'));\n\n constructor() {\n super();\n afterNextRender(() => {\n this.observeElementSizeChanges();\n this.observeBreakpointChanges();\n this.syncFromEmbla();\n\n if (this.window) {\n fromEvent(this.window, 'resize')\n .pipe(auditTime(50), takeUntil(this.destroy$))\n .subscribe(() => this.observeElementSizeChanges());\n }\n });\n }\n\n /**\n * Angular lifecycle hook: initializes RTL detection and the Embla plugins.\n */\n ngOnInit(): void {\n this.emblaOptions = {\n ...this.emblaOptions,\n direction: this.isRtl ? 'rtl' : 'ltr',\n };\n const autoplayDesired = this.isAutoplayDesired();\n // Reflect the initial play state so the current slide's loading bar is\n // shown on load (the plugin self-starts via playOnInit).\n this.isPlaying.set(autoplayDesired);\n this.emblaPlugins = [\n Autoplay({\n delay: this.playInterval(),\n // Autoplay is on by default (matching ECL) unless explicitly\n // disabled via [isAutoplay]=\"false\". Letting the plugin start\n // itself avoids a race with Embla's own initialization.\n playOnInit: autoplayDesired,\n stopOnInteraction: false,\n stopOnMouseEnter: true,\n stopOnFocusIn: true,\n }),\n ];\n this.handleRtlChangeState();\n }\n\n /**\n * Angular lifecycle hook: called after content (slides) is initialized.\n */\n ngAfterContentInit(): void {\n setTimeout(() => {\n this.initSlides();\n });\n }\n\n /**\n * Angular lifecycle hook: cleans up subscriptions.\n */\n ngOnDestroy(): void {\n this.destroy$.next(true);\n this.destroy$.unsubscribe();\n }\n\n /**\n * Returns whether the carousel has padding applied (e.g., on desktop breakpoint).\n */\n isPadded(): boolean {\n return this.isDesktopBreakpoint;\n }\n\n /**\n * Checks whether there are enough slides to render controls.\n */\n hasControls(): boolean {\n return this.getSlidesCount() > 1;\n }\n\n /**\n * Retrieves the current active slide index (zero-based).\n */\n getCurrentSlideIndex(): number {\n return this.activeIndex;\n }\n\n /**\n * Returns the 1-based position of the active slide (for the counter).\n */\n getCounterCurrent(): number {\n return this.activeIndex + 1;\n }\n\n /**\n * Returns the total number of slides (for the counter).\n */\n getCounterTotal(): number {\n return this.getSlidesCount();\n }\n\n /**\n * Dot indices for the mobile slider pager (one dot per slide). The dots are\n * hidden on desktop via the ECL CSS (`.ecl-carousel__dots { display: none }`)\n * and shown at the mobile breakpoint.\n */\n getDotIndices(): number[] {\n return Array.from({ length: this.getSlidesCount() }, (_unused, dotIndex) => dotIndex);\n }\n\n /**\n * Pre-translated accessible label for each pager dot. Translated here since\n * the shared `ecl-slider-pager` receives plain strings.\n */\n getDotAriaLabels(): string[] {\n const total = this.getSlidesCount();\n return Array.from({ length: total }, (_unused, dotIndex) =>\n this.translate.instant('ecl.carousel.GO-TO-SLIDE', {\n current: dotIndex + 1,\n total,\n }),\n );\n }\n\n /**\n * Returns whether a teaser button should be visible in the current window.\n */\n isTeaserVisible(index: number): boolean {\n const count = this.getSlidesCount();\n if (count <= VISIBLE_TEASERS) {\n return true;\n }\n const position = ((index - this.teaserWindowStart) % count + count) % count;\n return position < VISIBLE_TEASERS;\n }\n\n /**\n * Returns the flex order of a teaser button within the visible window.\n */\n getTeaserOrder(index: number): number {\n const count = this.getSlidesCount();\n return ((index - this.teaserWindowStart) % count + count) % count;\n }\n\n /**\n * Whether the pager should show the \"pause\" control (i.e. autoplay is\n * actively running). While the pointer/focus hover-pauses the carousel the\n * timer is suspended, so the pager must show \"play\" even though a manual\n * pause was not triggered. Mirrors upstream ECL, where hovering flips the\n * control to play.\n */\n protected isPagerPlaying(): boolean {\n return this.isPlaying() && !this.isHoverPaused();\n }\n\n /**\n * Pager variant by breakpoint. Below the ECL desktop breakpoint (1140px)\n * the carousel shows only the dots navigation (prev / dots / next);\n * play/pause is a desktop-only control in ECL. Rendering `'dots'` there\n * avoids the pager's inline play/pause `display` style (which would\n * otherwise beat the ECL CSS and leave the pause button visible on narrower\n * viewports). At/above 1140px the dots are hidden by CSS, so\n * `'play-pause-dots'` yields play/pause + prev/next + teasers.\n *\n * The 1140px boundary mirrors the media query the ECL CSS uses to toggle\n * `.ecl-carousel__play/__pause` and `.ecl-carousel__teasers`.\n */\n protected pagerVariant(): 'dots' | 'play-pause-dots' {\n return this.isDesktopBreakpointActive() ? 'play-pause-dots' : 'dots';\n }\n\n /**\n * Handles the throttled Embla events the component subscribed to.\n */\n protected onEmblaEvent(event: EmblaEventType): void {\n switch (event) {\n case 'select':\n case 'reInit':\n this.syncFromEmbla();\n break;\n case 'autoplay:timerset':\n // Fired each time the autoplay timer (re)starts for the current\n // slide; replay the loading-bar fill animation from zero.\n this.restartActiveLoadingBar();\n break;\n default:\n break;\n }\n }\n\n /**\n * Starts autoplay.\n *\n * @param moveFocus when true (a user clicked play) focus moves to the pause\n * button. Auto-starts (e.g. on breakpoint changes) pass false so the\n * page is not scrolled to the carousel on load.\n */\n protected onPlayClick(moveFocus = true): void {\n this.getAutoplay()?.play();\n this.isPlaying.set(true);\n this.isPausedForced.set(false);\n this.isHoverPaused.set(false);\n this.cd.detectChanges();\n if (moveFocus) {\n this.focusPagerButton('.ecl-carousel__pause');\n }\n }\n\n /**\n * Stops autoplay and focuses the play button.\n */\n protected onPauseClick(): void {\n // Pause (not full stop): halt the autoplay timer but keep the active\n // teaser loading bar visible, frozen at its current progress (matching\n // upstream ECL's `handleAutoPlay(false, true)`). The installed\n // embla-carousel-autoplay exposes no `pause()`, so we call `stop()`\n // WITHOUT `reset()` — the frozen bar is driven by our `is-paused` CSS\n // class, not the plugin. A full stop+reset (which clears the bar) only\n // happens on prev/next/teaser navigation via `stopAutoplay()`.\n this.getAutoplay()?.stop();\n this.isPlaying.set(false);\n this.isPausedForced.set(true);\n this.isHoverPaused.set(false);\n this.cd.detectChanges();\n this.focusPagerButton('.ecl-carousel__play');\n }\n\n /**\n * Navigates to the next slide and stops autoplay.\n */\n protected onNextSlideClick(): void {\n this.stopAutoplay();\n this.embla()?.scrollNext();\n }\n\n /**\n * Navigates to the previous slide and stops autoplay.\n */\n protected onPreviousSlideClick(): void {\n this.stopAutoplay();\n this.embla()?.scrollPrev();\n }\n\n /**\n * Handles user interaction with a teaser button: activates the matching slide.\n */\n protected onTeaserClick(index: number): void {\n this.stopAutoplay();\n this.embla()?.scrollTo(index);\n }\n\n /**\n * Handles a dot activation from the mobile slider pager: scrolls to the\n * matching slide and stops autoplay (same behavior as a teaser click).\n */\n protected onDotClick(index: number): void {\n this.stopAutoplay();\n this.embla()?.scrollTo(index);\n }\n\n /**\n * Handles keyboard navigation (ArrowLeft/ArrowRight) across teaser buttons.\n */\n protected onTeaserKeyboardPress(event: KeyboardEvent, index: number): void {\n if (event.key !== KeyCode.ARROW_RIGHT && event.key !== KeyCode.ARROW_LEFT) {\n return;\n }\n event.preventDefault();\n const direction = event.key === KeyCode.ARROW_RIGHT ? 1 : -1;\n const count = this.getSlidesCount();\n const nextIndex = (index + direction + count) % count;\n this.stopAutoplay();\n this.embla()?.scrollTo(nextIndex);\n this.focusOnButton(this.teaserButtons()[nextIndex]);\n }\n\n /**\n * Freezes the loading bar while the pointer/focus is over the carousel.\n * The Embla autoplay plugin pauses the timer itself (stopOnMouseEnter /\n * stopOnFocusIn); this only mirrors that as a visible paused state.\n */\n protected onViewportPause(): void {\n if (this.isPlaying() && !this.isPausedForced()) {\n this.isHoverPaused.set(true);\n this.cd.detectChanges();\n }\n }\n\n /**\n * Resumes the loading bar once the pointer/focus leaves the carousel.\n */\n protected onViewportResume(): void {\n if (this.isHoverPaused()) {\n this.isHoverPaused.set(false);\n this.cd.detectChanges();\n }\n }\n\n /**\n * Sets focus on the specified button.\n */\n private focusOnButton(button: ElementRef): void {\n if (!button) {\n return;\n }\n button.nativeElement.focus();\n }\n\n /**\n * Focuses the slider pager button matching the given ECL class selector\n * (e.g. `.ecl-carousel__play`). Used after a play/pause toggle so focus\n * follows the now-visible control, since the pager swaps play/pause\n * visibility via CSS. No-op if the button is not rendered (e.g. mobile).\n */\n private focusPagerButton(selector: string): void {\n const button = this.hostEl.nativeElement.querySelector<HTMLElement>(selector);\n button?.focus();\n }\n\n /**\n * Returns the Embla autoplay plugin instance, but only when the carousel\n * actually has more than one scroll snap.\n *\n * The `embla-carousel-autoplay` plugin skips its own initialization when\n * `scrollSnapList().length <= 1` (see the plugin's `init`), which leaves its\n * internal `delay` lookup table undefined. Calling `play()` in that state\n * throws (`delay[selectedScrollSnap()]` -> reading `0` of undefined). This\n * happens whenever Embla cannot lay out multiple snaps — notably in a jsdom\n * test or during SSR. Gating on the same `> 1` condition the plugin uses\n * keeps autoplay a safe no-op until the carousel is genuinely slideable.\n */\n private getAutoplay() {\n const api = this.embla()?.emblaApi;\n if (!api || api.scrollSnapList().length <= 1) {\n return undefined;\n }\n return api.plugins()?.autoplay;\n }\n\n /**\n * Whether autoplay should run. On by default (matching ECL); only disabled\n * when the consumer explicitly sets `[isAutoplay]=\"false\"`.\n */\n private isAutoplayDesired(): boolean {\n const isAutoplay = this.isAutoplay();\n return isAutoplay || isAutoplay === undefined;\n }\n\n /**\n * Stops autoplay and resets it (mirrors ECL's `handleAutoPlay(true)`),\n * clearing the loading-bar state.\n */\n private stopAutoplay(): void {\n const autoplay = this.getAutoplay();\n autoplay?.stop();\n autoplay?.reset();\n this.isPlaying.set(false);\n this.isPausedForced.set(false);\n this.isHoverPaused.set(false);\n this.cd.detectChanges();\n }\n\n /**\n * Synchronizes component state from Embla's current selected snap:\n * active index, slide `isCurrent` flags, teaser window and the slide event.\n */\n private syncFromEmbla(): void {\n const api = this.embla()?.emblaApi;\n if (!api) {\n return;\n }\n const selected = api.selectedScrollSnap();\n this.setActiveIndex(selected);\n this.updateTeaserWindow();\n this.slide.emit(new EclSlideEvent(this.slides().at(this.activeIndex)));\n this.cd.detectChanges();\n }\n\n /**\n * Subscribes to RTL layout state changes to adapt carousel direction.\n */\n private handleRtlChangeState(): void {\n this.eclRtlService.currentRtlState$.pipe(takeUntil(this.destroy$)).subscribe((rtlState) => {\n const changed = this.isRtl !== rtlState;\n this.isRtl = rtlState;\n if (changed) {\n this.emblaOptions = {\n ...this.emblaOptions,\n direction: this.isRtl ? 'rtl' : 'ltr',\n };\n this.cd.detectChanges();\n }\n });\n }\n\n /**\n * Initializes slides (index, ARIA label, banner height management).\n */\n private initSlides(): void {\n const slides = this.slides();\n slides.forEach((item, idx) => {\n item.index.set(idx);\n item.ariaLabel.set(`slide ${idx + 1} of ${slides.length}`);\n item.isCurrent.set(idx === 0);\n const banner = item.banner();\n if (banner) {\n banner.skipHeightManagement = true;\n }\n });\n this.cd.detectChanges();\n }\n\n /**\n * Updates the active index and synchronizes slide active/inert state.\n */\n private setActiveIndex(index: number): void {\n this.activeIndex = index;\n this.slides().forEach((item, idx) => {\n item.isCurrent.set(idx === index);\n });\n }\n\n /**\n * Restarts the CSS progress animation on the active slide's loading bar.\n * The animation uses `forwards`, so re-applying the `is-active` class alone\n * will not replay it; the animation must be reset explicitly.\n */\n private restartActiveLoadingBar(): void {\n if (!this.getAutoplay()?.isPlaying()) {\n return;\n }\n const bar = this.loadingBars()[this.activeIndex]?.nativeElement as HTMLElement | undefined;\n if (!bar) {\n return;\n }\n bar.style.animation = 'none';\n // Force reflow so the browser registers the reset before re-enabling.\n void bar.offsetWidth;\n bar.style.animation = '';\n }\n\n /**\n * Advances the visible teaser window when the active slide moves out of it.\n */\n private updateTeaserWindow(): void {\n const count = this.getSlidesCount();\n if (count <= VISIBLE_TEASERS) {\n this.teaserWindowStart = 0;\n return;\n }\n const firstHiddenIndex = (this.teaserWindowStart + VISIBLE_TEASERS) % count;\n if (this.activeIndex === firstHiddenIndex) {\n this.teaserWindowStart = firstHiddenIndex;\n }\n }\n\n /**\n * Returns the number of actual visible slides.\n */\n private getSlidesCount(): number {\n return this.slides()?.length ?? 0;\n }\n\n /**\n * Observes screen breakpoint changes to adjust autoplay and layout behavior.\n */\n private observeBreakpointChanges(): void {\n this.cdkBreakpointObserver\n .observe([\n '(max-width: 768px)',\n '(min-width: 998px)',\n '(min-width: 1140px)',\n '(min-width: 1140px) and (max-width: 1260px)',\n ])\n .pipe(takeUntil(this.destroy$))\n .subscribe((result) => {\n this.isMobileBreakpoint.set(result.breakpoints['(max-width: 768px)']);\n this.isTabletBreakpoint = result.breakpoints['(min-width: 998px)'];\n this.isLargeBreakpoint = result.breakpoints['(min-width: 1140px)'];\n this.isDesktopBreakpointActive.set(result.breakpoints['(min-width: 1140px)']);\n this.isDesktopBreakpoint = result.breakpoints['(min-width: 1140px) and (max-width: 1260px)'];\n\n // Deactivate autoplay on mobile; (re)activate it on larger\n // screens if autoplay is desired. Focus is never moved here so\n // the page is not scrolled to the carousel on load/resize.\n if (this.isMobileBreakpoint()) {\n this.stopAutoplay();\n return;\n }\n\n if (this.isTabletBreakpoint && this.isAutoplayDesired() && !this.isPausedForced()) {\n this.onPlayClick(false);\n }\n });\n }\n\n /**\n * Observes changes to the carousel's viewport element size using a timeout debounce.\n */\n private observeElementSizeChanges(): void {\n setTimeout(() => {\n if (this.isLargeBreakpoint) {\n this.checkBannerHeights();\n } else {\n this.resetHeight();\n }\n this.embla()?.reInit();\n }, this.TIMEOUT_INTERVAL);\n }\n\n /**\n * Normalizes the height of all banners to match the tallest banner.\n */\n private checkBannerHeights(): void {\n this.slides().forEach((slide) => {\n const banner = slide.banner();\n if (banner) {\n banner.calculateHeight();\n }\n });\n\n const heightValues = this.slides().map((slide) => {\n const el = slide.banner()?.el.nativeElement;\n if (!el) return undefined;\n\n const styleHeight = el.style.height;\n\n if (styleHeight === 'auto' || styleHeight === '') {\n return el.offsetHeight;\n }\n\n const height = parseInt(styleHeight, 10);\n if (Number.isNaN(height) || height === 100) {\n return 1;\n }\n\n return height;\n });\n\n const elementHeights = heightValues.filter((h) => h !== undefined) as number[];\n const tallestElementHeight = Math.max(...elementHeights);\n\n const slides = this.slides();\n if (tallestElementHeight > 1 && elementHeights.length === slides.length) {\n slides.forEach((slide) => {\n const banner = slide.banner();\n if (!banner) return;\n\n const el = banner.el.nativeElement;\n const bannerImage = banner.eclBannerImage();\n const bannerVideo = banner.eclBannerVideo();\n\n this.renderer2.setStyle(el, 'height', `${tallestElementHeight}px`);\n this.renderer2.setStyle(el, 'aspect-ratio', 'auto');\n\n if (bannerImage) {\n this.renderer2.setStyle(bannerImage.el.nativeElement, 'aspect-ratio', 'auto');\n }\n if (bannerVideo) {\n this.renderer2.setStyle(bannerVideo.el.nativeElement, 'aspect-ratio', 'auto');\n }\n });\n }\n }\n\n /**\n * Clears all explicit banner height and aspect ratio styling for smaller breakpoints.\n */\n private resetHeight(): void {\n this.slides().forEach((slide) => {\n const banner = slide.banner();\n if (banner) {\n const bannerImage = banner.eclBannerImage();\n const bannerVideo = banner.eclBannerVideo();\n this.renderer2.removeStyle(banner.el.nativeElement, 'height');\n this.renderer2.removeStyle(banner.el.nativeElement, 'aspect-ratio');\n if (bannerImage) {\n this.renderer2.removeStyle(bannerImage.el.nativeElement, 'aspect-ratio');\n }\n if (bannerVideo) {\n this.renderer2.removeStyle(bannerVideo.el.nativeElement, 'aspect-ratio');\n }\n }\n });\n }\n}\n",
54618
+ "sourceCode": "import { BreakpointObserver } from '@angular/cdk/layout';\nimport {\n AfterContentInit,\n ChangeDetectorRef,\n Component,\n DOCUMENT,\n ElementRef,\n OnDestroy,\n OnInit,\n Renderer2,\n afterNextRender,\n booleanAttribute,\n computed,\n contentChildren,\n forwardRef,\n inject,\n input,\n numberAttribute,\n output,\n signal,\n viewChild,\n viewChildren,\n} from '@angular/core';\nimport { EUI_ECL_SLIDER } from '@eui/ecl/components/ecl-slider';\nimport { ECLBaseDirective, EclRtlService, KeyCode } from '@eui/ecl/core';\nimport { TranslatePipe, TranslateService } from '@ngx-translate/core';\nimport {\n EmblaEventType,\n EmblaOptionsType,\n EmblaPluginType,\n} from 'embla-carousel';\nimport { EmblaCarouselDirective } from 'embla-carousel-angular';\nimport Autoplay from 'embla-carousel-autoplay';\nimport { Subject, auditTime, fromEvent, takeUntil } from 'rxjs';\nimport { EclCarouselItemComponent } from './ecl-carousel-item.component';\nimport { EclSlideEvent } from './events/ecl-slide.event';\n\n/**\n * Number of teaser buttons visible at once on the desktop breakpoint.\n */\nconst VISIBLE_TEASERS = 4;\n\n/**\n * Carousel (slideshow) component following the ECL design system.\n * Renders a sequence of `EclCarouselItemComponent` slides with autoplay, keyboard navigation,\n * responsive breakpoint awareness, and RTL layout support.\n *\n * The slide engine is powered by Embla (via `embla-carousel-angular`), matching the upstream\n * ECL implementation: an infinite `loop`, the `Autoplay` plugin driving the teaser loading bars,\n * and a slider pager with previous/next/play/pause controls, a counter, and a tablist of teaser\n * buttons (one per slide).\n *\n * - Selector: `ecl-carousel`.\n * - Project `ecl-carousel-item` elements as slides (also via `contentChildren`).\n * - `ariaLabel` — ARIA label for the carousel landmark; aliased as `aria-label`.\n * - `isAutoplay` — enables automatic slide advancement; boolean attribute.\n * - `playInterval` — milliseconds between automatic transitions; default `5000`.\n * - `slide` — emits `EclSlideEvent` when the active slide changes.\n */\n@Component({\n selector: 'ecl-carousel',\n templateUrl: './ecl-carousel.component.html',\n imports: [\n TranslatePipe,\n EmblaCarouselDirective,\n ...EUI_ECL_SLIDER,\n ],\n host: {\n '[attr.aria-label]': 'ariaLabel()',\n '[attr.aria-roledescription]': '\"carousel\"',\n '[class]': 'cssClasses()',\n // Drives the teaser loading-bar animation duration (see @eui/styles).\n // It has no CSS fallback, so without it the progress animation never\n // runs; it must match the autoplay delay.\n '[style.--ecl-carousel-slide-duration]': 'playInterval() + \"ms\"',\n },\n})\nexport class EclCarouselComponent extends ECLBaseDirective implements OnInit, OnDestroy, AfterContentInit {\n /**\n * Aria-label for screen readers to describe the carousel.\n */\n readonly ariaLabel = input<string | null>('Carousel description here', { alias: 'aria-label' });\n\n /**\n * Determines whether the carousel should autoplay.\n */\n readonly isAutoplay = input<boolean, unknown>(undefined, { transform: booleanAttribute });\n\n /**\n * Interval in milliseconds between automatic slide transitions.\n */\n readonly playInterval = input(5000, { transform: numberAttribute });\n\n /**\n * Event emitted when a slide change occurs.\n */\n readonly slide = output<EclSlideEvent>();\n\n /**\n * Reference to the main carousel viewport element (the Embla root).\n */\n readonly carouselContainerElement = viewChild<ElementRef>('carouselContainer');\n\n /**\n * Collection of slide components rendered inside the carousel.\n */\n readonly slides = contentChildren(forwardRef(() => EclCarouselItemComponent));\n\n /**\n * Indicates whether autoplay is currently active.\n */\n readonly isPlaying = signal(false);\n\n /**\n * Indicates whether autoplay was manually paused (via the pause button or\n * on mobile). When paused this way the loading bar is cleared.\n */\n readonly isPausedForced = signal(false);\n\n /**\n * Indicates whether the layout is currently in RTL mode.\n */\n isRtl = false;\n\n /**\n * Zero-based index of the currently active slide (mirrors Embla's selected snap).\n */\n private activeIndex = 0;\n\n /**\n * Start index of the currently visible teaser window (desktop only).\n */\n private teaserWindowStart = 0;\n\n /**\n * Indicates whether autoplay is temporarily paused by hover/focus. Unlike a\n * manual pause, the loading bar stays visible and frozen at its current\n * progress (matching ECL), resuming when the pointer/focus leaves.\n */\n protected readonly isHoverPaused = signal(false);\n\n /**\n * Whether the current viewport is at the mobile breakpoint. A signal so the\n * template can react (the pager drops play/pause on mobile, matching ECL,\n * where play/pause is a desktop-only control).\n */\n protected readonly isMobileBreakpoint = signal(false);\n\n /**\n * Whether the viewport is at or above the ECL desktop breakpoint (1140px).\n * This is the boundary the ECL CSS uses to show the play/pause controls and\n * the teaser tablist (and to hide the mobile dots). A signal so the pager\n * variant can react: play/pause only renders at/above this width, matching\n * the CSS so the pause button never leaks onto narrower viewports.\n */\n protected readonly isDesktopBreakpointActive = signal(false);\n\n /**\n * Embla options, mirroring the upstream ECL configuration.\n */\n protected emblaOptions: EmblaOptionsType = {\n loop: true,\n align: 'start',\n duration: 20,\n };\n\n /**\n * Embla plugins. Built once so the Embla directive does not needlessly re-init.\n */\n protected emblaPlugins: EmblaPluginType[] = [];\n\n /**\n * Embla events the component reacts to.\n */\n protected readonly emblaEvents: EmblaEventType[] = [\n 'select',\n 'reInit',\n 'autoplay:timerset',\n ];\n\n /**\n * Reference to the Embla directive driving the slide engine.\n */\n private readonly embla = viewChild<EmblaCarouselDirective>('embla');\n\n /**\n * References to the teaser navigation buttons corresponding to each slide.\n */\n private readonly teaserButtons = viewChildren<ElementRef>('teaserButton');\n\n /**\n * References to the teaser loading-bar completion elements (one per slide),\n * used to restart the progress animation when the active slide changes.\n */\n private readonly loadingBars = viewChildren<ElementRef>('loadingBar');\n\n private isTabletBreakpoint = false;\n private isLargeBreakpoint = false;\n private isDesktopBreakpoint = false;\n\n private destroy$: Subject<boolean> = new Subject<boolean>();\n\n /**\n * Debounce interval (ms) used internally for timing and transitions.\n */\n private readonly TIMEOUT_INTERVAL = 400;\n private readonly cdkBreakpointObserver = inject(BreakpointObserver);\n private readonly cd = inject(ChangeDetectorRef);\n private readonly renderer2 = inject(Renderer2);\n private readonly eclRtlService = inject(EclRtlService);\n private readonly translate = inject(TranslateService);\n private readonly document = inject<Document>(DOCUMENT);\n private readonly window = this.document.defaultView;\n /**\n * Host element, used to locate the slider pager's play/pause buttons by their\n * ECL classes for focus management after a play/pause toggle.\n */\n private readonly hostEl = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /**\n * Returns the computed CSS class list for the component.\n */\n protected readonly cssClasses = computed<string>(() =>\n super.getCssClasses('ecl-carousel'));\n\n constructor() {\n super();\n afterNextRender(() => {\n this.observeElementSizeChanges();\n this.observeBreakpointChanges();\n this.syncFromEmbla();\n\n if (this.window) {\n fromEvent(this.window, 'resize')\n .pipe(auditTime(50), takeUntil(this.destroy$))\n .subscribe(() => this.observeElementSizeChanges());\n }\n });\n }\n\n /**\n * Angular lifecycle hook: initializes RTL detection and the Embla plugins.\n */\n ngOnInit(): void {\n this.emblaOptions = {\n ...this.emblaOptions,\n direction: this.isRtl ? 'rtl' : 'ltr',\n };\n const autoplayDesired = this.isAutoplayDesired();\n // Reflect the initial play state so the current slide's loading bar is\n // shown on load (the plugin self-starts via playOnInit).\n this.isPlaying.set(autoplayDesired);\n this.emblaPlugins = [\n Autoplay({\n delay: this.playInterval(),\n // Autoplay is on by default (matching ECL) unless explicitly\n // disabled via [isAutoplay]=\"false\". Letting the plugin start\n // itself avoids a race with Embla's own initialization.\n playOnInit: autoplayDesired,\n stopOnInteraction: false,\n stopOnMouseEnter: true,\n stopOnFocusIn: true,\n }),\n ];\n this.handleRtlChangeState();\n }\n\n /**\n * Angular lifecycle hook: called after content (slides) is initialized.\n */\n ngAfterContentInit(): void {\n setTimeout(() => {\n this.initSlides();\n });\n }\n\n /**\n * Angular lifecycle hook: cleans up subscriptions.\n */\n ngOnDestroy(): void {\n this.destroy$.next(true);\n this.destroy$.unsubscribe();\n }\n\n /**\n * Returns whether the carousel has padding applied (e.g., on desktop breakpoint).\n */\n isPadded(): boolean {\n return this.isDesktopBreakpoint;\n }\n\n /**\n * Checks whether there are enough slides to render controls.\n */\n hasControls(): boolean {\n return this.getSlidesCount() > 1;\n }\n\n /**\n * Retrieves the current active slide index (zero-based).\n */\n getCurrentSlideIndex(): number {\n return this.activeIndex;\n }\n\n /**\n * Returns the 1-based position of the active slide (for the counter).\n */\n getCounterCurrent(): number {\n return this.activeIndex + 1;\n }\n\n /**\n * Returns the total number of slides (for the counter).\n */\n getCounterTotal(): number {\n return this.getSlidesCount();\n }\n\n /**\n * Dot indices for the mobile slider pager (one dot per slide). The dots are\n * hidden on desktop via the ECL CSS (`.ecl-carousel__dots { display: none }`)\n * and shown at the mobile breakpoint.\n */\n getDotIndices(): number[] {\n return Array.from({ length: this.getSlidesCount() }, (_unused, dotIndex) => dotIndex);\n }\n\n /**\n * Pre-translated accessible label for each pager dot. Translated here since\n * the shared `ecl-slider-pager` receives plain strings.\n */\n getDotAriaLabels(): string[] {\n const total = this.getSlidesCount();\n return Array.from({ length: total }, (_unused, dotIndex) =>\n this.translate.instant('ecl.carousel.GO-TO-SLIDE', {\n current: dotIndex + 1,\n total,\n }),\n );\n }\n\n /**\n * Returns whether a teaser button should be visible in the current window.\n */\n isTeaserVisible(index: number): boolean {\n const count = this.getSlidesCount();\n if (count <= VISIBLE_TEASERS) {\n return true;\n }\n const position = ((index - this.teaserWindowStart) % count + count) % count;\n return position < VISIBLE_TEASERS;\n }\n\n /**\n * Returns the flex order of a teaser button within the visible window.\n */\n getTeaserOrder(index: number): number {\n const count = this.getSlidesCount();\n return ((index - this.teaserWindowStart) % count + count) % count;\n }\n\n /**\n * Whether the pager should show the \"pause\" control (i.e. autoplay is\n * actively running). While the pointer/focus hover-pauses the carousel the\n * timer is suspended, so the pager must show \"play\" even though a manual\n * pause was not triggered. Mirrors upstream ECL, where hovering flips the\n * control to play.\n */\n protected isPagerPlaying(): boolean {\n return this.isPlaying() && !this.isHoverPaused();\n }\n\n /**\n * Pager variant by breakpoint. Below the ECL desktop breakpoint (1140px)\n * the carousel shows only the dots navigation (prev / dots / next);\n * play/pause is a desktop-only control in ECL. Rendering `'dots'` there\n * avoids the pager's inline play/pause `display` style (which would\n * otherwise beat the ECL CSS and leave the pause button visible on narrower\n * viewports). At/above 1140px the dots are hidden by CSS, so\n * `'play-pause-dots'` yields play/pause + prev/next + teasers.\n *\n * The 1140px boundary mirrors the media query the ECL CSS uses to toggle\n * `.ecl-carousel__play/__pause` and `.ecl-carousel__teasers`.\n */\n protected pagerVariant(): 'dots' | 'play-pause-dots' {\n return this.isDesktopBreakpointActive() ? 'play-pause-dots' : 'dots';\n }\n\n /**\n * Handles the throttled Embla events the component subscribed to.\n */\n protected onEmblaEvent(event: EmblaEventType): void {\n switch (event) {\n case 'select':\n case 'reInit':\n this.syncFromEmbla();\n break;\n case 'autoplay:timerset':\n // Fired each time the autoplay timer (re)starts for the current\n // slide; replay the loading-bar fill animation from zero.\n this.restartActiveLoadingBar();\n break;\n default:\n break;\n }\n }\n\n /**\n * Starts autoplay.\n *\n * @param moveFocus when true (a user clicked play) focus moves to the pause\n * button. Auto-starts (e.g. on breakpoint changes) pass false so the\n * page is not scrolled to the carousel on load.\n */\n protected onPlayClick(moveFocus = true): void {\n this.getAutoplay()?.play();\n this.isPlaying.set(true);\n this.isPausedForced.set(false);\n this.isHoverPaused.set(false);\n this.cd.detectChanges();\n if (moveFocus) {\n this.focusPagerButton('.ecl-carousel__pause');\n }\n }\n\n /**\n * Stops autoplay and focuses the play button.\n */\n protected onPauseClick(): void {\n // Pause (not full stop): halt the autoplay timer but keep the active\n // teaser loading bar visible, frozen at its current progress (matching\n // upstream ECL's `handleAutoPlay(false, true)`). The installed\n // embla-carousel-autoplay exposes no `pause()`, so we call `stop()`\n // WITHOUT `reset()` — the frozen bar is driven by our `is-paused` CSS\n // class, not the plugin. A full stop+reset (which clears the bar) only\n // happens on prev/next/teaser navigation via `stopAutoplay()`.\n this.getAutoplay()?.stop();\n this.isPlaying.set(false);\n this.isPausedForced.set(true);\n this.isHoverPaused.set(false);\n this.cd.detectChanges();\n this.focusPagerButton('.ecl-carousel__play');\n }\n\n /**\n * Navigates to the next slide and stops autoplay.\n */\n protected onNextSlideClick(): void {\n this.stopAutoplay();\n this.embla()?.scrollNext();\n }\n\n /**\n * Navigates to the previous slide and stops autoplay.\n */\n protected onPreviousSlideClick(): void {\n this.stopAutoplay();\n this.embla()?.scrollPrev();\n }\n\n /**\n * Handles user interaction with a teaser button: activates the matching slide.\n */\n protected onTeaserClick(index: number): void {\n this.stopAutoplay();\n this.embla()?.scrollTo(index);\n }\n\n /**\n * Handles a dot activation from the mobile slider pager: scrolls to the\n * matching slide and stops autoplay (same behavior as a teaser click).\n */\n protected onDotClick(index: number): void {\n this.stopAutoplay();\n this.embla()?.scrollTo(index);\n }\n\n /**\n * Handles keyboard navigation (ArrowLeft/ArrowRight) across teaser buttons.\n */\n protected onTeaserKeyboardPress(event: KeyboardEvent, index: number): void {\n if (event.key !== KeyCode.ARROW_RIGHT && event.key !== KeyCode.ARROW_LEFT) {\n return;\n }\n event.preventDefault();\n const direction = event.key === KeyCode.ARROW_RIGHT ? 1 : -1;\n const count = this.getSlidesCount();\n const nextIndex = (index + direction + count) % count;\n this.stopAutoplay();\n this.embla()?.scrollTo(nextIndex);\n this.focusOnButton(this.teaserButtons()[nextIndex]);\n }\n\n /**\n * Freezes the loading bar while the pointer/focus is over the carousel.\n * The Embla autoplay plugin pauses the timer itself (stopOnMouseEnter /\n * stopOnFocusIn); this only mirrors that as a visible paused state.\n */\n protected onViewportPause(): void {\n if (this.isPlaying() && !this.isPausedForced()) {\n this.isHoverPaused.set(true);\n this.cd.detectChanges();\n }\n }\n\n /**\n * Resumes the loading bar once the pointer/focus leaves the carousel.\n */\n protected onViewportResume(): void {\n if (this.isHoverPaused()) {\n this.isHoverPaused.set(false);\n this.cd.detectChanges();\n }\n }\n\n /**\n * Sets focus on the specified button.\n */\n private focusOnButton(button: ElementRef): void {\n if (!button) {\n return;\n }\n button.nativeElement.focus();\n }\n\n /**\n * Focuses the slider pager button matching the given ECL class selector\n * (e.g. `.ecl-carousel__play`). Used after a play/pause toggle so focus\n * follows the now-visible control, since the pager swaps play/pause\n * visibility via CSS. No-op if the button is not rendered (e.g. mobile).\n */\n private focusPagerButton(selector: string): void {\n const button = this.hostEl.nativeElement.querySelector<HTMLElement>(selector);\n button?.focus();\n }\n\n /**\n * Returns the Embla autoplay plugin instance, but only when the carousel\n * actually has more than one scroll snap.\n *\n * The `embla-carousel-autoplay` plugin skips its own initialization when\n * `scrollSnapList().length <= 1` (see the plugin's `init`), which leaves its\n * internal `delay` lookup table undefined. Calling `play()` in that state\n * throws (`delay[selectedScrollSnap()]` -> reading `0` of undefined). This\n * happens whenever Embla cannot lay out multiple snaps — notably in a jsdom\n * test or during SSR. Gating on the same `> 1` condition the plugin uses\n * keeps autoplay a safe no-op until the carousel is genuinely slideable.\n */\n private getAutoplay() {\n const api = this.embla()?.emblaApi;\n if (!api || api.scrollSnapList().length <= 1) {\n return undefined;\n }\n return api.plugins()?.autoplay;\n }\n\n /**\n * Whether autoplay should run. On by default (matching ECL); only disabled\n * when the consumer explicitly sets `[isAutoplay]=\"false\"`.\n */\n private isAutoplayDesired(): boolean {\n const isAutoplay = this.isAutoplay();\n return isAutoplay || isAutoplay === undefined;\n }\n\n /**\n * Stops autoplay and resets it (mirrors ECL's `handleAutoPlay(true)`),\n * clearing the loading-bar state.\n */\n private stopAutoplay(): void {\n const autoplay = this.getAutoplay();\n autoplay?.stop();\n autoplay?.reset();\n this.isPlaying.set(false);\n this.isPausedForced.set(false);\n this.isHoverPaused.set(false);\n this.cd.detectChanges();\n }\n\n /**\n * Synchronizes component state from Embla's current selected snap:\n * active index, slide `isCurrent` flags, teaser window and the slide event.\n */\n private syncFromEmbla(): void {\n const api = this.embla()?.emblaApi;\n if (!api) {\n return;\n }\n const selected = api.selectedScrollSnap();\n this.setActiveIndex(selected);\n this.updateTeaserWindow();\n this.slide.emit(new EclSlideEvent(this.slides().at(this.activeIndex)));\n this.cd.detectChanges();\n }\n\n /**\n * Subscribes to RTL layout state changes to adapt carousel direction.\n */\n private handleRtlChangeState(): void {\n this.eclRtlService.currentRtlState$.pipe(takeUntil(this.destroy$)).subscribe((rtlState) => {\n const changed = this.isRtl !== rtlState;\n this.isRtl = rtlState;\n if (changed) {\n this.emblaOptions = {\n ...this.emblaOptions,\n direction: this.isRtl ? 'rtl' : 'ltr',\n };\n this.cd.detectChanges();\n }\n });\n }\n\n /**\n * Initializes slides (index, ARIA label, banner height management).\n */\n private initSlides(): void {\n const slides = this.slides();\n slides.forEach((item, idx) => {\n item.index.set(idx);\n item.ariaLabel.set(`slide ${idx + 1} of ${slides.length}`);\n item.isCurrent.set(idx === 0);\n const banner = item.banner();\n if (banner) {\n banner.skipHeightManagement = true;\n }\n });\n this.cd.detectChanges();\n }\n\n /**\n * Updates the active index and synchronizes slide active/inert state.\n */\n private setActiveIndex(index: number): void {\n this.activeIndex = index;\n this.slides().forEach((item, idx) => {\n item.isCurrent.set(idx === index);\n });\n }\n\n /**\n * Restarts the CSS progress animation on the active slide's loading bar.\n * The animation uses `forwards`, so re-applying the `is-active` class alone\n * will not replay it; the animation must be reset explicitly.\n */\n private restartActiveLoadingBar(): void {\n if (!this.getAutoplay()?.isPlaying()) {\n return;\n }\n const bar = this.loadingBars()[this.activeIndex]?.nativeElement as HTMLElement | undefined;\n if (!bar) {\n return;\n }\n bar.style.animation = 'none';\n // Force reflow so the browser registers the reset before re-enabling.\n void bar.offsetWidth;\n bar.style.animation = '';\n }\n\n /**\n * Advances the visible teaser window when the active slide moves out of it.\n */\n private updateTeaserWindow(): void {\n const count = this.getSlidesCount();\n if (count <= VISIBLE_TEASERS) {\n this.teaserWindowStart = 0;\n return;\n }\n const firstHiddenIndex = (this.teaserWindowStart + VISIBLE_TEASERS) % count;\n if (this.activeIndex === firstHiddenIndex) {\n this.teaserWindowStart = firstHiddenIndex;\n }\n }\n\n /**\n * Returns the number of actual visible slides.\n */\n private getSlidesCount(): number {\n return this.slides()?.length ?? 0;\n }\n\n /**\n * Observes screen breakpoint changes to adjust autoplay and layout behavior.\n */\n private observeBreakpointChanges(): void {\n this.cdkBreakpointObserver\n .observe([\n '(max-width: 768px)',\n '(min-width: 998px)',\n '(min-width: 1140px)',\n '(min-width: 1140px) and (max-width: 1260px)',\n ])\n .pipe(takeUntil(this.destroy$))\n .subscribe((result) => {\n this.isMobileBreakpoint.set(result.breakpoints['(max-width: 768px)']);\n this.isTabletBreakpoint = result.breakpoints['(min-width: 998px)'];\n this.isLargeBreakpoint = result.breakpoints['(min-width: 1140px)'];\n this.isDesktopBreakpointActive.set(result.breakpoints['(min-width: 1140px)']);\n this.isDesktopBreakpoint = result.breakpoints['(min-width: 1140px) and (max-width: 1260px)'];\n\n // Deactivate autoplay on mobile; (re)activate it on larger\n // screens if autoplay is desired. Focus is never moved here so\n // the page is not scrolled to the carousel on load/resize.\n if (this.isMobileBreakpoint()) {\n this.stopAutoplay();\n return;\n }\n\n if (this.isTabletBreakpoint && this.isAutoplayDesired() && !this.isPausedForced()) {\n this.onPlayClick(false);\n }\n });\n }\n\n /**\n * Observes changes to the carousel's viewport element size using a timeout debounce.\n */\n private observeElementSizeChanges(): void {\n setTimeout(() => {\n if (this.isLargeBreakpoint) {\n this.checkBannerHeights();\n } else {\n this.resetHeight();\n }\n this.embla()?.reInit();\n }, this.TIMEOUT_INTERVAL);\n }\n\n /**\n * Normalizes the height of all banners to match the tallest banner.\n */\n private checkBannerHeights(): void {\n this.slides().forEach((slide) => {\n const banner = slide.banner();\n if (banner) {\n banner.calculateHeight();\n }\n });\n\n const heightValues = this.slides().map((slide) => {\n const el = slide.banner()?.el.nativeElement;\n if (!el) return undefined;\n\n const styleHeight = el.style.height;\n\n if (styleHeight === 'auto' || styleHeight === '') {\n return el.offsetHeight;\n }\n\n const height = parseInt(styleHeight, 10);\n if (Number.isNaN(height) || height === 100) {\n return 1;\n }\n\n return height;\n });\n\n const elementHeights = heightValues.filter((h) => h !== undefined) as number[];\n const tallestElementHeight = Math.max(...elementHeights);\n\n const slides = this.slides();\n if (tallestElementHeight > 1 && elementHeights.length === slides.length) {\n slides.forEach((slide) => {\n const banner = slide.banner();\n if (!banner) return;\n\n const el = banner.el.nativeElement;\n const bannerImage = banner.eclBannerImage();\n const bannerVideo = banner.eclBannerVideo();\n\n this.renderer2.setStyle(el, 'height', `${tallestElementHeight}px`);\n this.renderer2.setStyle(el, 'aspect-ratio', 'auto');\n\n if (bannerImage) {\n this.renderer2.setStyle(bannerImage.el.nativeElement, 'aspect-ratio', 'auto');\n }\n if (bannerVideo) {\n this.renderer2.setStyle(bannerVideo.el.nativeElement, 'aspect-ratio', 'auto');\n }\n });\n }\n }\n\n /**\n * Clears all explicit banner height and aspect ratio styling for smaller breakpoints.\n */\n private resetHeight(): void {\n this.slides().forEach((slide) => {\n const banner = slide.banner();\n if (banner) {\n const bannerImage = banner.eclBannerImage();\n const bannerVideo = banner.eclBannerVideo();\n this.renderer2.removeStyle(banner.el.nativeElement, 'height');\n this.renderer2.removeStyle(banner.el.nativeElement, 'aspect-ratio');\n if (bannerImage) {\n this.renderer2.removeStyle(bannerImage.el.nativeElement, 'aspect-ratio');\n }\n if (bannerVideo) {\n this.renderer2.removeStyle(bannerVideo.el.nativeElement, 'aspect-ratio');\n }\n }\n });\n }\n}\n",
54648
54619
  "assetsDirs": [],
54649
54620
  "styleUrlsData": "",
54650
54621
  "stylesData": "",
54651
54622
  "jsdoctags": [
54652
54623
  {
54653
54624
  "pos": 1143,
54654
- "end": 2204,
54625
+ "end": 2116,
54655
54626
  "kind": 321,
54656
54627
  "id": 0,
54657
54628
  "flags": 16777216,
54658
54629
  "modifierFlagsCache": 0,
54659
54630
  "transformFlags": 0,
54660
- "comment": "Carousel (slideshow) component following the ECL design system.\nRenders a sequence of `EclCarouselItemComponent` slides with autoplay, keyboard navigation,\nresponsive breakpoint awareness, and RTL layout support.\n\nThe slide engine is powered by Embla (via `embla-carousel-angular`), matching the upstream\nECL implementation: an infinite `loop`, the `Autoplay` plugin driving the teaser loading bars,\nand a slider pager with previous/next/play/pause controls, a counter, and a tablist of teaser\nbuttons (one per slide).\n\n- Selector: `ecl-carousel`.\n- Project `ecl-carousel-item` elements as slides (also via `contentChildren`).\n- `ariaLabel` — ARIA label for the carousel landmark; aliased as `aria-label`.\n- `isAutoplay` — enables automatic slide advancement; boolean attribute.\n- `playInterval` — milliseconds between automatic transitions; default `5000`.\n- `counterLabel` — separator label used by the counter, defaults to `ecl.common.OF`.\n- `slide` — emits `EclSlideEvent` when the active slide changes."
54631
+ "comment": "Carousel (slideshow) component following the ECL design system.\nRenders a sequence of `EclCarouselItemComponent` slides with autoplay, keyboard navigation,\nresponsive breakpoint awareness, and RTL layout support.\n\nThe slide engine is powered by Embla (via `embla-carousel-angular`), matching the upstream\nECL implementation: an infinite `loop`, the `Autoplay` plugin driving the teaser loading bars,\nand a slider pager with previous/next/play/pause controls, a counter, and a tablist of teaser\nbuttons (one per slide).\n\n- Selector: `ecl-carousel`.\n- Project `ecl-carousel-item` elements as slides (also via `contentChildren`).\n- `ariaLabel` — ARIA label for the carousel landmark; aliased as `aria-label`.\n- `isAutoplay` — enables automatic slide advancement; boolean attribute.\n- `playInterval` — milliseconds between automatic transitions; default `5000`.\n- `slide` — emits `EclSlideEvent` when the active slide changes."
54661
54632
  }
54662
54633
  ],
54663
54634
  "constructorObj": {
@@ -54666,7 +54637,7 @@
54666
54637
  "deprecated": false,
54667
54638
  "deprecationMessage": "",
54668
54639
  "args": [],
54669
- "line": 231,
54640
+ "line": 224,
54670
54641
  "rawdescription": "\n"
54671
54642
  },
54672
54643
  "extends": [
@@ -54711,11 +54682,11 @@
54711
54682
  }
54712
54683
  ]
54713
54684
  },
54714
- "templateData": "@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"
54685
+ "templateData": "@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() }} {{ ('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"
54715
54686
  },
54716
54687
  {
54717
54688
  "name": "EclCarouselItemComponent",
54718
- "id": "component-EclCarouselItemComponent-ca5a31b1844197993f48ffec30e3bd9847f83e1f68c487cb42a717797a989ccced551189baf4d85536c1569a98714cfa55563d1e073d1b6b26681475e26917ad",
54689
+ "id": "component-EclCarouselItemComponent-d2023eb2cd274d05c0eca2908ac3630ab8141ddeac689b4a6dc53a388ceb91c13061aaaf072796462beee071da08e196f8360d3354892c3d57f55f53fab54f08",
54719
54690
  "file": "packages/ecl/components/ecl-carousel/ecl-carousel-item.component.ts",
54720
54691
  "encapsulation": [],
54721
54692
  "entryComponents": [],
@@ -54735,22 +54706,6 @@
54735
54706
  "viewProviders": [],
54736
54707
  "hostDirectives": [],
54737
54708
  "inputsClass": [
54738
- {
54739
- "name": "id",
54740
- "coverageIgnore": false,
54741
- "deprecated": false,
54742
- "deprecationMessage": "",
54743
- "type": "string",
54744
- "indexKey": "",
54745
- "optional": false,
54746
- "description": "<p>The unique identifier of the slide.</p>\n",
54747
- "line": 64,
54748
- "rawdescription": "\n\nThe unique identifier of the slide.\n",
54749
- "modifierKind": [
54750
- 148
54751
- ],
54752
- "required": false
54753
- },
54754
54709
  {
54755
54710
  "name": "isCurrent",
54756
54711
  "coverageIgnore": false,
@@ -54761,7 +54716,7 @@
54761
54716
  "indexKey": "",
54762
54717
  "optional": false,
54763
54718
  "description": "<p>Input controlling whether this slide starts as the active/visible one.\nAutomatically coerced to a boolean value.</p>\n",
54764
- "line": 70,
54719
+ "line": 64,
54765
54720
  "rawdescription": "\n\nInput controlling whether this slide starts as the active/visible one.\nAutomatically coerced to a boolean value.\n",
54766
54721
  "modifierKind": [
54767
54722
  148
@@ -54778,7 +54733,7 @@
54778
54733
  "indexKey": "",
54779
54734
  "optional": false,
54780
54735
  "description": "<p>Text shown in the carousel teaser (navigation) button for this slide.\nOptional; when empty, no teaser label is rendered for the slide.</p>\n",
54781
- "line": 83,
54736
+ "line": 77,
54782
54737
  "rawdescription": "\n\nText shown in the carousel teaser (navigation) button for this slide.\nOptional; when empty, no teaser label is rendered for the slide.\n",
54783
54738
  "modifierKind": [
54784
54739
  148
@@ -54854,7 +54809,7 @@
54854
54809
  "indexKey": "",
54855
54810
  "optional": false,
54856
54811
  "description": "<p>Emits an event when the slide is clicked.</p>\n",
54857
- "line": 88,
54812
+ "line": 82,
54858
54813
  "rawdescription": "\n\nEmits an event when the slide is clicked.\n",
54859
54814
  "modifierKind": [
54860
54815
  148
@@ -54893,7 +54848,7 @@
54893
54848
  "indexKey": "",
54894
54849
  "optional": false,
54895
54850
  "description": "<p>The aria-label for the slide, assigned dynamically by the carousel.</p>\n",
54896
- "line": 59,
54851
+ "line": 58,
54897
54852
  "rawdescription": "\n\nThe aria-label for the slide, assigned dynamically by the carousel.\n",
54898
54853
  "modifierKind": [
54899
54854
  148
@@ -54909,7 +54864,7 @@
54909
54864
  "indexKey": "",
54910
54865
  "optional": false,
54911
54866
  "description": "<p>Optionally captures a banner component projected into the slide.</p>\n",
54912
- "line": 93,
54867
+ "line": 87,
54913
54868
  "rawdescription": "\n\nOptionally captures a banner component projected into the slide.\n",
54914
54869
  "modifierKind": [
54915
54870
  148
@@ -54925,7 +54880,7 @@
54925
54880
  "indexKey": "",
54926
54881
  "optional": false,
54927
54882
  "description": "<p>Computes the slide&#39;s CSS class.</p>\n",
54928
- "line": 103,
54883
+ "line": 97,
54929
54884
  "rawdescription": "\n\nComputes the slide's CSS class.\n",
54930
54885
  "modifierKind": [
54931
54886
  124,
@@ -54942,7 +54897,7 @@
54942
54897
  "indexKey": "",
54943
54898
  "optional": false,
54944
54899
  "description": "",
54945
- "line": 54,
54900
+ "line": 53,
54946
54901
  "rawdescription": "\n"
54947
54902
  },
54948
54903
  {
@@ -54955,7 +54910,7 @@
54955
54910
  "indexKey": "",
54956
54911
  "optional": false,
54957
54912
  "description": "<p>Internal slide index, assigned dynamically by the carousel.</p>\n",
54958
- "line": 98,
54913
+ "line": 92,
54959
54914
  "rawdescription": "\n\nInternal slide index, assigned dynamically by the carousel.\n",
54960
54915
  "modifierKind": [
54961
54916
  148
@@ -54971,7 +54926,7 @@
54971
54926
  "indexKey": "",
54972
54927
  "optional": false,
54973
54928
  "description": "<p>Indicates whether this slide is currently active/visible.\nSeeded from the <code>isCurrent</code> input but can be updated imperatively by the\nparent carousel as the user navigates between slides.</p>\n",
54974
- "line": 77,
54929
+ "line": 71,
54975
54930
  "rawdescription": "\n\nIndicates whether this slide is currently active/visible.\nSeeded from the `isCurrent` input but can be updated imperatively by the\nparent carousel as the user navigates between slides.\n",
54976
54931
  "modifierKind": [
54977
54932
  148
@@ -54986,7 +54941,7 @@
54986
54941
  "optional": false,
54987
54942
  "returnType": "void",
54988
54943
  "typeParameters": [],
54989
- "line": 109,
54944
+ "line": 103,
54990
54945
  "deprecated": false,
54991
54946
  "deprecationMessage": "",
54992
54947
  "rawdescription": "\n\nHandles click events on the slide and emits a slideClick event.\n",
@@ -55065,23 +55020,23 @@
55065
55020
  "hostListeners": [],
55066
55021
  "standalone": false,
55067
55022
  "imports": [],
55068
- "description": "<p>Single slide item for use inside an <code>EclCarouselComponent</code>.\nRenders a <code>role=&quot;tabpanel&quot;</code> element with an <code>aria-roledescription=&quot;slide&quot;</code> and handles\nthe <code>inert</code> attribute so only the active slide is interactive.</p>\n<ul>\n<li>Selector: <code>ecl-carousel-item</code>.</li>\n<li><code>id</code> — optional unique identifier; used by the carousel for navigation buttons.</li>\n<li><code>isCurrent</code> (input alias) — initial active state; kept as a <code>linkedSignal</code> so the parent\ncarousel can update it imperatively as the user navigates.</li>\n<li><code>slideClick</code> — emits <code>EclSlideEvent</code> when the slide is clicked.</li>\n<li><code>index</code> / <code>ariaLabel</code> — writable signals assigned by the parent carousel.</li>\n<li>Project an optional <code>EclBannerComponent</code> inside the slide for banner-style slides.</li>\n</ul>\n",
55069
- "rawdescription": "\n\nSingle slide item for use inside an `EclCarouselComponent`.\nRenders a `role=\"tabpanel\"` element with an `aria-roledescription=\"slide\"` and handles\nthe `inert` attribute so only the active slide is interactive.\n\n- Selector: `ecl-carousel-item`.\n- `id` — optional unique identifier; used by the carousel for navigation buttons.\n- `isCurrent` (input alias) — initial active state; kept as a `linkedSignal` so the parent\n carousel can update it imperatively as the user navigates.\n- `slideClick` — emits `EclSlideEvent` when the slide is clicked.\n- `index` / `ariaLabel` — writable signals assigned by the parent carousel.\n- Project an optional `EclBannerComponent` inside the slide for banner-style slides.\n",
55023
+ "description": "<p>Single slide item for use inside an <code>EclCarouselComponent</code>.\nRenders a <code>role=&quot;tabpanel&quot;</code> element with an <code>aria-roledescription=&quot;slide&quot;</code> and handles\nthe <code>inert</code> attribute so only the active slide is interactive.</p>\n<ul>\n<li>Selector: <code>ecl-carousel-item</code>.</li>\n<li><code>isCurrent</code> (input alias) — initial active state; kept as a <code>linkedSignal</code> so the parent\ncarousel can update it imperatively as the user navigates.</li>\n<li><code>slideClick</code> — emits <code>EclSlideEvent</code> when the slide is clicked.</li>\n<li><code>index</code> / <code>ariaLabel</code> — writable signals assigned by the parent carousel.</li>\n<li>Project an optional <code>EclBannerComponent</code> inside the slide for banner-style slides.</li>\n</ul>\n",
55024
+ "rawdescription": "\n\nSingle slide item for use inside an `EclCarouselComponent`.\nRenders a `role=\"tabpanel\"` element with an `aria-roledescription=\"slide\"` and handles\nthe `inert` attribute so only the active slide is interactive.\n\n- Selector: `ecl-carousel-item`.\n- `isCurrent` (input alias) — initial active state; kept as a `linkedSignal` so the parent\n carousel can update it imperatively as the user navigates.\n- `slideClick` — emits `EclSlideEvent` when the slide is clicked.\n- `index` / `ariaLabel` — writable signals assigned by the parent carousel.\n- Project an optional `EclBannerComponent` inside the slide for banner-style slides.\n",
55070
55025
  "type": "component",
55071
- "sourceCode": "import {\n Component,\n ElementRef,\n booleanAttribute,\n computed,\n forwardRef,\n inject,\n input,\n linkedSignal,\n output,\n signal,\n contentChild,\n} from '@angular/core';\nimport { ECLBaseDirective } from '@eui/ecl/core';\nimport { EclBannerComponent } from '@eui/ecl/components/ecl-banner';\nimport { EclSlideEvent } from './events/ecl-slide.event';\nimport { EclCarouselItem } from './models/ecl-carousel-item.model';\n\n/**\n * Single slide item for use inside an `EclCarouselComponent`.\n * Renders a `role=\"tabpanel\"` element with an `aria-roledescription=\"slide\"` and handles\n * the `inert` attribute so only the active slide is interactive.\n *\n * - Selector: `ecl-carousel-item`.\n * - `id` — optional unique identifier; used by the carousel for navigation buttons.\n * - `isCurrent` (input alias) — initial active state; kept as a `linkedSignal` so the parent\n * carousel can update it imperatively as the user navigates.\n * - `slideClick` — emits `EclSlideEvent` when the slide is clicked.\n * - `index` / `ariaLabel` — writable signals assigned by the parent carousel.\n * - Project an optional `EclBannerComponent` inside the slide for banner-style slides.\n */\n@Component({\n selector: 'ecl-carousel-item',\n templateUrl: './ecl-carousel-item.component.html',\n host: {\n role: 'tabpanel',\n '[attr.aria-label]': 'ariaLabel()',\n '[attr.aria-roledescription]': '\"slide\"',\n '[attr.inert]': '!isCurrent() || undefined',\n '[class]': 'cssClasses()',\n '(click)': 'onSlideClick()',\n },\n styles: [\n `\n /* TODO: remove by implementing full ECL specs */\n :host:before {\n left: 0 !important;\n display: none;\n }\n `,\n ],\n})\nexport class EclCarouselItemComponent extends ECLBaseDirective implements EclCarouselItem {\n el = inject(ElementRef);\n\n /**\n * The aria-label for the slide, assigned dynamically by the carousel.\n */\n readonly ariaLabel = signal<string | undefined>(undefined);\n\n /**\n * The unique identifier of the slide.\n */\n readonly id = input<string>();\n\n /**\n * Input controlling whether this slide starts as the active/visible one.\n * Automatically coerced to a boolean value.\n */\n readonly isCurrentInput = input(false, { alias: 'isCurrent', transform: booleanAttribute });\n\n /**\n * Indicates whether this slide is currently active/visible.\n * Seeded from the `isCurrent` input but can be updated imperatively by the\n * parent carousel as the user navigates between slides.\n */\n readonly isCurrent = linkedSignal(() => this.isCurrentInput());\n\n /**\n * Text shown in the carousel teaser (navigation) button for this slide.\n * Optional; when empty, no teaser label is rendered for the slide.\n */\n readonly teaserText = input<string>('');\n\n /**\n * Emits an event when the slide is clicked.\n */\n readonly slideClick = output<EclSlideEvent>();\n\n /**\n * Optionally captures a banner component projected into the slide.\n */\n readonly banner = contentChild(forwardRef(() => EclBannerComponent));\n\n /**\n * Internal slide index, assigned dynamically by the carousel.\n */\n readonly index = signal<number | undefined>(undefined);\n\n /**\n * Computes the slide's CSS class.\n */\n protected readonly cssClasses = computed<string>(() =>\n super.getCssClasses('ecl-carousel__slide'));\n\n /**\n * Handles click events on the slide and emits a slideClick event.\n */\n onSlideClick(): void {\n this.slideClick.emit(new EclSlideEvent(this));\n }\n}\n",
55026
+ "sourceCode": "import {\n Component,\n ElementRef,\n booleanAttribute,\n computed,\n forwardRef,\n inject,\n input,\n linkedSignal,\n output,\n signal,\n contentChild,\n} from '@angular/core';\nimport { ECLBaseDirective } from '@eui/ecl/core';\nimport { EclBannerComponent } from '@eui/ecl/components/ecl-banner';\nimport { EclSlideEvent } from './events/ecl-slide.event';\nimport { EclCarouselItem } from './models/ecl-carousel-item.model';\n\n/**\n * Single slide item for use inside an `EclCarouselComponent`.\n * Renders a `role=\"tabpanel\"` element with an `aria-roledescription=\"slide\"` and handles\n * the `inert` attribute so only the active slide is interactive.\n *\n * - Selector: `ecl-carousel-item`.\n * - `isCurrent` (input alias) — initial active state; kept as a `linkedSignal` so the parent\n * carousel can update it imperatively as the user navigates.\n * - `slideClick` — emits `EclSlideEvent` when the slide is clicked.\n * - `index` / `ariaLabel` — writable signals assigned by the parent carousel.\n * - Project an optional `EclBannerComponent` inside the slide for banner-style slides.\n */\n@Component({\n selector: 'ecl-carousel-item',\n templateUrl: './ecl-carousel-item.component.html',\n host: {\n role: 'tabpanel',\n '[attr.aria-label]': 'ariaLabel()',\n '[attr.aria-roledescription]': '\"slide\"',\n '[attr.inert]': '!isCurrent() || undefined',\n '[class]': 'cssClasses()',\n '(click)': 'onSlideClick()',\n },\n styles: [\n `\n /* TODO: remove by implementing full ECL specs */\n :host:before {\n left: 0 !important;\n display: none;\n }\n `,\n ],\n})\nexport class EclCarouselItemComponent extends ECLBaseDirective implements EclCarouselItem {\n el = inject(ElementRef);\n\n /**\n * The aria-label for the slide, assigned dynamically by the carousel.\n */\n readonly ariaLabel = signal<string | undefined>(undefined);\n\n /**\n * Input controlling whether this slide starts as the active/visible one.\n * Automatically coerced to a boolean value.\n */\n readonly isCurrentInput = input(false, { alias: 'isCurrent', transform: booleanAttribute });\n\n /**\n * Indicates whether this slide is currently active/visible.\n * Seeded from the `isCurrent` input but can be updated imperatively by the\n * parent carousel as the user navigates between slides.\n */\n readonly isCurrent = linkedSignal(() => this.isCurrentInput());\n\n /**\n * Text shown in the carousel teaser (navigation) button for this slide.\n * Optional; when empty, no teaser label is rendered for the slide.\n */\n readonly teaserText = input<string>('');\n\n /**\n * Emits an event when the slide is clicked.\n */\n readonly slideClick = output<EclSlideEvent>();\n\n /**\n * Optionally captures a banner component projected into the slide.\n */\n readonly banner = contentChild(forwardRef(() => EclBannerComponent));\n\n /**\n * Internal slide index, assigned dynamically by the carousel.\n */\n readonly index = signal<number | undefined>(undefined);\n\n /**\n * Computes the slide's CSS class.\n */\n protected readonly cssClasses = computed<string>(() =>\n super.getCssClasses('ecl-carousel__slide'));\n\n /**\n * Handles click events on the slide and emits a slideClick event.\n */\n onSlideClick(): void {\n this.slideClick.emit(new EclSlideEvent(this));\n }\n}\n",
55072
55027
  "assetsDirs": [],
55073
55028
  "styleUrlsData": "",
55074
55029
  "stylesData": "\n /* TODO: remove by implementing full ECL specs */\n :host:before {\n left: 0 !important;\n display: none;\n }\n \n",
55075
55030
  "jsdoctags": [
55076
55031
  {
55077
55032
  "pos": 445,
55078
- "end": 1189,
55033
+ "end": 1104,
55079
55034
  "kind": 321,
55080
55035
  "id": 0,
55081
55036
  "flags": 16777216,
55082
55037
  "modifierFlagsCache": 0,
55083
55038
  "transformFlags": 0,
55084
- "comment": "Single slide item for use inside an `EclCarouselComponent`.\nRenders a `role=\"tabpanel\"` element with an `aria-roledescription=\"slide\"` and handles\nthe `inert` attribute so only the active slide is interactive.\n\n- Selector: `ecl-carousel-item`.\n- `id` — optional unique identifier; used by the carousel for navigation buttons.\n- `isCurrent` (input alias) — initial active state; kept as a `linkedSignal` so the parent\n carousel can update it imperatively as the user navigates.\n- `slideClick` — emits `EclSlideEvent` when the slide is clicked.\n- `index` / `ariaLabel` — writable signals assigned by the parent carousel.\n- Project an optional `EclBannerComponent` inside the slide for banner-style slides."
55039
+ "comment": "Single slide item for use inside an `EclCarouselComponent`.\nRenders a `role=\"tabpanel\"` element with an `aria-roledescription=\"slide\"` and handles\nthe `inert` attribute so only the active slide is interactive.\n\n- Selector: `ecl-carousel-item`.\n- `isCurrent` (input alias) — initial active state; kept as a `linkedSignal` so the parent\n carousel can update it imperatively as the user navigates.\n- `slideClick` — emits `EclSlideEvent` when the slide is clicked.\n- `index` / `ariaLabel` — writable signals assigned by the parent carousel.\n- Project an optional `EclBannerComponent` inside the slide for banner-style slides."
55085
55040
  }
55086
55041
  ],
55087
55042
  "extends": [
@@ -106960,7 +106915,7 @@
106960
106915
  "name": "ARROW_SIZE",
106961
106916
  "ctype": "miscellaneous",
106962
106917
  "subtype": "variable",
106963
- "file": "packages/ecl/components/ecl-site-header/language/ecl-site-header-language.component.ts",
106918
+ "file": "packages/ecl/components/ecl-site-header/login/ecl-site-header-login.component.ts",
106964
106919
  "coverageIgnore": false,
106965
106920
  "deprecated": false,
106966
106921
  "deprecationMessage": "",
@@ -106973,7 +106928,7 @@
106973
106928
  "name": "ARROW_SIZE",
106974
106929
  "ctype": "miscellaneous",
106975
106930
  "subtype": "variable",
106976
- "file": "packages/ecl/components/ecl-site-header/login/ecl-site-header-login.component.ts",
106931
+ "file": "packages/ecl/components/ecl-site-header/language/ecl-site-header-language.component.ts",
106977
106932
  "coverageIgnore": false,
106978
106933
  "deprecated": false,
106979
106934
  "deprecationMessage": "",
@@ -108441,21 +108396,6 @@
108441
108396
  }
108442
108397
  ],
108443
108398
  "groupedVariables": {
108444
- "packages/ecl/components/ecl-site-header/language/ecl-site-header-language.component.ts": [
108445
- {
108446
- "name": "ARROW_SIZE",
108447
- "ctype": "miscellaneous",
108448
- "subtype": "variable",
108449
- "file": "packages/ecl/components/ecl-site-header/language/ecl-site-header-language.component.ts",
108450
- "coverageIgnore": false,
108451
- "deprecated": false,
108452
- "deprecationMessage": "",
108453
- "type": "string",
108454
- "defaultValue": "'0.5rem'",
108455
- "rawdescription": "Half the arrow's own width, matching ECL's `arrowSize`.",
108456
- "description": "<p>Half the arrow&#39;s own width, matching ECL&#39;s <code>arrowSize</code>.</p>\n"
108457
- }
108458
- ],
108459
108399
  "packages/ecl/components/ecl-site-header/login/ecl-site-header-login.component.ts": [
108460
108400
  {
108461
108401
  "name": "ARROW_SIZE",
@@ -108484,6 +108424,21 @@
108484
108424
  "description": "<p>Debounce for the resize recomputation, matching ECL&#39;s <code>handleResize</code>.</p>\n"
108485
108425
  }
108486
108426
  ],
108427
+ "packages/ecl/components/ecl-site-header/language/ecl-site-header-language.component.ts": [
108428
+ {
108429
+ "name": "ARROW_SIZE",
108430
+ "ctype": "miscellaneous",
108431
+ "subtype": "variable",
108432
+ "file": "packages/ecl/components/ecl-site-header/language/ecl-site-header-language.component.ts",
108433
+ "coverageIgnore": false,
108434
+ "deprecated": false,
108435
+ "deprecationMessage": "",
108436
+ "type": "string",
108437
+ "defaultValue": "'0.5rem'",
108438
+ "rawdescription": "Half the arrow's own width, matching ECL's `arrowSize`.",
108439
+ "description": "<p>Half the arrow&#39;s own width, matching ECL&#39;s <code>arrowSize</code>.</p>\n"
108440
+ }
108441
+ ],
108487
108442
  "packages/ecl/components/ecl-site-header/search/ecl-site-header-search.component.ts": [
108488
108443
  {
108489
108444
  "name": "ARROW_SIZE",