@eui/ecl 22.0.0-alpha.37 → 22.0.0-alpha.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/changelog.html +28 -0
- package/docs/components/EclSloganTickerComponent.html +38 -2
- package/docs/components/EclSocialMediaFollowItemComponent.html +2 -2
- package/docs/directives/EclSocialMediaFollowLinkDirective.html +12 -47
- package/docs/js/search/search_index.js +2 -2
- package/docs/json/EclSloganTickerComponent.md +47 -2
- package/docs/json/EclSocialMediaFollowItemComponent.md +1 -1
- package/docs/json/documentation.json +74 -98
- package/docs/llms.txt +3 -3
- package/docs/properties.html +1 -1
- package/fesm2022/eui-ecl-components-ecl-slogan-ticker.mjs +53 -2
- package/fesm2022/eui-ecl-components-ecl-slogan-ticker.mjs.map +1 -1
- package/fesm2022/eui-ecl-components-ecl-social-media-follow.mjs +13 -14
- package/fesm2022/eui-ecl-components-ecl-social-media-follow.mjs.map +1 -1
- package/package.json +1 -1
- package/types/eui-ecl-components-ecl-slogan-ticker.d.ts +53 -2
- package/types/eui-ecl-components-ecl-slogan-ticker.d.ts.map +1 -1
- package/types/eui-ecl-components-ecl-social-media-follow.d.ts +3 -4
- package/types/eui-ecl-components-ecl-social-media-follow.d.ts.map +1 -1
|
@@ -8457,13 +8457,13 @@
|
|
|
8457
8457
|
},
|
|
8458
8458
|
{
|
|
8459
8459
|
"name": "EclSocialMediaFollowItemClickEvent",
|
|
8460
|
-
"id": "class-EclSocialMediaFollowItemClickEvent-
|
|
8460
|
+
"id": "class-EclSocialMediaFollowItemClickEvent-4d681eab78f3c19c56ea7257583c64f115fd7e7d71c472b1e4283066c6a140e35ac096a345d68e7097d14833840a5ade158853c6012a4edad152b38642f45176",
|
|
8461
8461
|
"file": "packages/ecl/components/ecl-social-media-follow/ecl-social-media-follow-item.component.ts",
|
|
8462
8462
|
"coverageIgnore": false,
|
|
8463
8463
|
"deprecated": false,
|
|
8464
8464
|
"deprecationMessage": "",
|
|
8465
8465
|
"type": "class",
|
|
8466
|
-
"sourceCode": "import { booleanAttribute, Component, inject, input, output } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { EclIconSet, EUI_ECL_ICON } from '@eui/ecl/components/ecl-icon';\nimport { EclBaseEvent } from '@eui/ecl/core';\nimport { ECLBaseDirective } from '@eui/ecl/core';\nimport { EUI_ECL_LINK } from '@eui/ecl/components/ecl-link';\nimport { RouterLink, RouterLinkWithHref } from '@angular/router';\nimport { EclSocialMediaFollowComponent } from './ecl-social-media-follow.component';\n\nexport class EclSocialMediaFollowItemClickEvent extends EclBaseEvent {}\n\n@Component({\n selector: 'ecl-social-media-follow-item',\n templateUrl: './ecl-social-media-follow-item.component.html',\n imports: [...EUI_ECL_ICON, ...EUI_ECL_LINK, NgTemplateOutlet, RouterLink, RouterLinkWithHref],\n host: {\n role: 'listitem',\n '[class]': 'cssClasses',\n },\n})\nexport class EclSocialMediaFollowItemComponent extends ECLBaseDirective {\n readonly isInverted = input
|
|
8466
|
+
"sourceCode": "import { booleanAttribute, Component, inject, input, output } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { EclIconSet, EUI_ECL_ICON } from '@eui/ecl/components/ecl-icon';\nimport { EclBaseEvent } from '@eui/ecl/core';\nimport { ECLBaseDirective } from '@eui/ecl/core';\nimport { EUI_ECL_LINK } from '@eui/ecl/components/ecl-link';\nimport { RouterLink, RouterLinkWithHref } from '@angular/router';\nimport { EclSocialMediaFollowComponent } from './ecl-social-media-follow.component';\n\nexport class EclSocialMediaFollowItemClickEvent extends EclBaseEvent {}\n\n@Component({\n selector: 'ecl-social-media-follow-item',\n templateUrl: './ecl-social-media-follow-item.component.html',\n imports: [...EUI_ECL_ICON, ...EUI_ECL_LINK, NgTemplateOutlet, RouterLink, RouterLinkWithHref],\n host: {\n role: 'listitem',\n '[class]': 'cssClasses',\n },\n})\nexport class EclSocialMediaFollowItemComponent extends ECLBaseDirective {\n readonly isInverted = input(false, { transform: booleanAttribute });\n icon = input<string>();\n iconSet = input<EclIconSet>('social-media');\n readonly href = input('');\n readonly routerLink = input('');\n readonly target = input<string>(undefined);\n readonly size = input('m');\n readonly extraAttributes = input<{\n name: string;\n value?: string | null;\n }[]>(undefined);\n\n get cssClasses(): string {\n return [super.getCssClasses('ecl-social-media-follow__item')].join(' ').trim();\n }\n\n readonly itemClick = output<EclSocialMediaFollowItemClickEvent>();\n\n private parent = inject(EclSocialMediaFollowComponent);\n\n get isIconOnly(): boolean {\n return this.parent?.isIconOnly() ?? false;\n }\n\n onItemClick(evt: Event): void {\n this.itemClick.emit(new EclSocialMediaFollowItemClickEvent());\n if (!this.href() && !this.routerLink()) {\n evt.preventDefault();\n }\n }\n}\n",
|
|
8467
8467
|
"displayName": "EclSocialMediaFollowItemClickEvent",
|
|
8468
8468
|
"inputsClass": [],
|
|
8469
8469
|
"outputsClass": [],
|
|
@@ -34810,12 +34810,12 @@
|
|
|
34810
34810
|
},
|
|
34811
34811
|
{
|
|
34812
34812
|
"name": "EclSocialMediaFollowLinkDirective",
|
|
34813
|
-
"id": "directive-EclSocialMediaFollowLinkDirective-
|
|
34813
|
+
"id": "directive-EclSocialMediaFollowLinkDirective-6cfdb821045849c86b69d08070f5778754edd43f0c1b171cb5d80921ebcf6abe3d4b98e460b336c745d3df87867ff59bbf393e026100ccf039e75230ff98537b",
|
|
34814
34814
|
"file": "packages/ecl/components/ecl-social-media-follow/ecl-social-media-follow-link.directive.ts",
|
|
34815
34815
|
"type": "directive",
|
|
34816
34816
|
"description": "",
|
|
34817
34817
|
"rawdescription": "\n",
|
|
34818
|
-
"sourceCode": "import { Directive,
|
|
34818
|
+
"sourceCode": "import { Directive, contentChild, effect } from '@angular/core';\nimport { ECLBaseDirective } from '@eui/ecl/core';\nimport { EclIconComponent } from '@eui/ecl/components/ecl-icon';\n\n@Directive({\n selector: '[eclSocialMediaFollowLink]',\n host: {\n '[class]': 'cssClasses',\n },\n})\nexport class EclSocialMediaFollowLinkDirective extends ECLBaseDirective {\n readonly eclIcon = contentChild(EclIconComponent);\n\n constructor() {\n super();\n // Add the icon modifier class to a projected icon reactively.\n effect(() => {\n this.eclIcon()?.addExtraClass('ecl-social-media-follow__icon');\n });\n }\n\n get cssClasses(): string {\n return [super.getCssClasses('ecl-social-media-follow__link')].join(' ').trim();\n }\n}\n",
|
|
34819
34819
|
"selector": "[eclSocialMediaFollowLink]",
|
|
34820
34820
|
"providers": [],
|
|
34821
34821
|
"hostDirectives": [],
|
|
@@ -34868,20 +34868,7 @@
|
|
|
34868
34868
|
"outputsClass": [],
|
|
34869
34869
|
"deprecated": false,
|
|
34870
34870
|
"deprecationMessage": "",
|
|
34871
|
-
"hostBindings": [
|
|
34872
|
-
{
|
|
34873
|
-
"coverageIgnore": false,
|
|
34874
|
-
"name": "class",
|
|
34875
|
-
"actualName": "cssClasses",
|
|
34876
|
-
"deprecated": false,
|
|
34877
|
-
"deprecationMessage": "",
|
|
34878
|
-
"rawdescription": "\n",
|
|
34879
|
-
"description": "",
|
|
34880
|
-
"line": 12,
|
|
34881
|
-
"type": "string",
|
|
34882
|
-
"decorators": []
|
|
34883
|
-
}
|
|
34884
|
-
],
|
|
34871
|
+
"hostBindings": [],
|
|
34885
34872
|
"hostListeners": [],
|
|
34886
34873
|
"propertiesClass": [
|
|
34887
34874
|
{
|
|
@@ -34894,7 +34881,7 @@
|
|
|
34894
34881
|
"indexKey": "",
|
|
34895
34882
|
"optional": false,
|
|
34896
34883
|
"description": "",
|
|
34897
|
-
"line":
|
|
34884
|
+
"line": 12,
|
|
34898
34885
|
"rawdescription": "\n",
|
|
34899
34886
|
"modifierKind": [
|
|
34900
34887
|
148
|
|
@@ -34902,19 +34889,6 @@
|
|
|
34902
34889
|
}
|
|
34903
34890
|
],
|
|
34904
34891
|
"methodsClass": [
|
|
34905
|
-
{
|
|
34906
|
-
"name": "ngAfterContentInit",
|
|
34907
|
-
"coverageIgnore": false,
|
|
34908
|
-
"args": [],
|
|
34909
|
-
"optional": false,
|
|
34910
|
-
"returnType": "void",
|
|
34911
|
-
"typeParameters": [],
|
|
34912
|
-
"line": 16,
|
|
34913
|
-
"deprecated": false,
|
|
34914
|
-
"deprecationMessage": "",
|
|
34915
|
-
"rawdescription": "\n",
|
|
34916
|
-
"description": ""
|
|
34917
|
-
},
|
|
34918
34892
|
{
|
|
34919
34893
|
"name": "getCssClasses",
|
|
34920
34894
|
"coverageIgnore": false,
|
|
@@ -34958,9 +34932,15 @@
|
|
|
34958
34932
|
"extends": [
|
|
34959
34933
|
"ECLBaseDirective"
|
|
34960
34934
|
],
|
|
34961
|
-
"
|
|
34962
|
-
"
|
|
34963
|
-
|
|
34935
|
+
"constructorObj": {
|
|
34936
|
+
"name": "constructor",
|
|
34937
|
+
"description": "",
|
|
34938
|
+
"deprecated": false,
|
|
34939
|
+
"deprecationMessage": "",
|
|
34940
|
+
"args": [],
|
|
34941
|
+
"line": 12,
|
|
34942
|
+
"rawdescription": "\n"
|
|
34943
|
+
},
|
|
34964
34944
|
"accessors": {
|
|
34965
34945
|
"cssClasses": {
|
|
34966
34946
|
"name": "cssClasses",
|
|
@@ -34968,7 +34948,7 @@
|
|
|
34968
34948
|
"name": "cssClasses",
|
|
34969
34949
|
"type": "string",
|
|
34970
34950
|
"returnType": "string",
|
|
34971
|
-
"line":
|
|
34951
|
+
"line": 22,
|
|
34972
34952
|
"rawdescription": "\n",
|
|
34973
34953
|
"description": ""
|
|
34974
34954
|
}
|
|
@@ -34980,10 +34960,6 @@
|
|
|
34980
34960
|
{
|
|
34981
34961
|
"name": "ECLBaseDirective",
|
|
34982
34962
|
"type": "directive"
|
|
34983
|
-
},
|
|
34984
|
-
{
|
|
34985
|
-
"name": "AfterContentInit",
|
|
34986
|
-
"type": "interface"
|
|
34987
34963
|
}
|
|
34988
34964
|
]
|
|
34989
34965
|
}
|
|
@@ -82085,7 +82061,7 @@
|
|
|
82085
82061
|
},
|
|
82086
82062
|
{
|
|
82087
82063
|
"name": "EclSloganTickerComponent",
|
|
82088
|
-
"id": "component-EclSloganTickerComponent-
|
|
82064
|
+
"id": "component-EclSloganTickerComponent-2eb62f84b1dcee763ecf77029dd0655bc6f9bd2b933146e247238ffaefe7f1f629ea2aa8e70a39bf43717642639cdd9796ee05cdafc391bca43415212d0ab8d7",
|
|
82089
82065
|
"file": "packages/ecl/components/ecl-slogan-ticker/ecl-slogan-ticker.component.ts",
|
|
82090
82066
|
"changeDetection": "ChangeDetectionStrategy.OnPush",
|
|
82091
82067
|
"encapsulation": [],
|
|
@@ -82114,7 +82090,7 @@
|
|
|
82114
82090
|
"indexKey": "",
|
|
82115
82091
|
"optional": false,
|
|
82116
82092
|
"description": "<p>Aria-label for the ticker region.</p>\n",
|
|
82117
|
-
"line":
|
|
82093
|
+
"line": 98,
|
|
82118
82094
|
"rawdescription": "\nAria-label for the ticker region.",
|
|
82119
82095
|
"required": false
|
|
82120
82096
|
},
|
|
@@ -82174,7 +82150,7 @@
|
|
|
82174
82150
|
"indexKey": "",
|
|
82175
82151
|
"optional": false,
|
|
82176
82152
|
"description": "<p>Embla carousel options.</p>\n",
|
|
82177
|
-
"line":
|
|
82153
|
+
"line": 101,
|
|
82178
82154
|
"subProperties": [
|
|
82179
82155
|
{
|
|
82180
82156
|
"name": "loop",
|
|
@@ -82220,7 +82196,7 @@
|
|
|
82220
82196
|
"indexKey": "",
|
|
82221
82197
|
"optional": false,
|
|
82222
82198
|
"description": "<p>Embla plugins array (auto-scroll).</p>\n",
|
|
82223
|
-
"line":
|
|
82199
|
+
"line": 109,
|
|
82224
82200
|
"rawdescription": "\nEmbla plugins array (auto-scroll).",
|
|
82225
82201
|
"modifierKind": [
|
|
82226
82202
|
124
|
|
@@ -82236,7 +82212,7 @@
|
|
|
82236
82212
|
"indexKey": "",
|
|
82237
82213
|
"optional": false,
|
|
82238
82214
|
"description": "",
|
|
82239
|
-
"line":
|
|
82215
|
+
"line": 111,
|
|
82240
82216
|
"rawdescription": "\n",
|
|
82241
82217
|
"modifierKind": [
|
|
82242
82218
|
124
|
|
@@ -82252,7 +82228,7 @@
|
|
|
82252
82228
|
"indexKey": "",
|
|
82253
82229
|
"optional": false,
|
|
82254
82230
|
"description": "<p>Whether the user explicitly paused.</p>\n",
|
|
82255
|
-
"line":
|
|
82231
|
+
"line": 121,
|
|
82256
82232
|
"rawdescription": "\nWhether the user explicitly paused.",
|
|
82257
82233
|
"modifierKind": [
|
|
82258
82234
|
124
|
|
@@ -82268,7 +82244,7 @@
|
|
|
82268
82244
|
"indexKey": "",
|
|
82269
82245
|
"optional": false,
|
|
82270
82246
|
"description": "<p>Whether auto-scroll is currently playing.</p>\n",
|
|
82271
|
-
"line":
|
|
82247
|
+
"line": 118,
|
|
82272
82248
|
"rawdescription": "\nWhether auto-scroll is currently playing.",
|
|
82273
82249
|
"modifierKind": [
|
|
82274
82250
|
124
|
|
@@ -82284,7 +82260,7 @@
|
|
|
82284
82260
|
"indexKey": "",
|
|
82285
82261
|
"optional": false,
|
|
82286
82262
|
"description": "",
|
|
82287
|
-
"line":
|
|
82263
|
+
"line": 113,
|
|
82288
82264
|
"rawdescription": "\n",
|
|
82289
82265
|
"modifierKind": [
|
|
82290
82266
|
124
|
|
@@ -82300,7 +82276,7 @@
|
|
|
82300
82276
|
"indexKey": "",
|
|
82301
82277
|
"optional": false,
|
|
82302
82278
|
"description": "",
|
|
82303
|
-
"line":
|
|
82279
|
+
"line": 112,
|
|
82304
82280
|
"rawdescription": "\n",
|
|
82305
82281
|
"modifierKind": [
|
|
82306
82282
|
124
|
|
@@ -82316,7 +82292,7 @@
|
|
|
82316
82292
|
"indexKey": "",
|
|
82317
82293
|
"optional": false,
|
|
82318
82294
|
"description": "<p>Whether play/pause controls should be visible.</p>\n",
|
|
82319
|
-
"line":
|
|
82295
|
+
"line": 124,
|
|
82320
82296
|
"rawdescription": "\nWhether play/pause controls should be visible.",
|
|
82321
82297
|
"modifierKind": [
|
|
82322
82298
|
124
|
|
@@ -82332,7 +82308,7 @@
|
|
|
82332
82308
|
"indexKey": "",
|
|
82333
82309
|
"optional": false,
|
|
82334
82310
|
"description": "<p>Subscribed Embla events.</p>\n",
|
|
82335
|
-
"line":
|
|
82311
|
+
"line": 134,
|
|
82336
82312
|
"rawdescription": "\nSubscribed Embla events.",
|
|
82337
82313
|
"modifierKind": [
|
|
82338
82314
|
124
|
|
@@ -82348,7 +82324,7 @@
|
|
|
82348
82324
|
"indexKey": "",
|
|
82349
82325
|
"optional": false,
|
|
82350
82326
|
"description": "<p>Array of slide texts used to render the track.\nContains the original slides plus clones for seamless looping.\nEach item has <code>text</code> and <code>isClone</code> (for aria-hidden on duplicates).</p>\n",
|
|
82351
|
-
"line":
|
|
82327
|
+
"line": 131,
|
|
82352
82328
|
"rawdescription": "\n\nArray of slide texts used to render the track.\nContains the original slides plus clones for seamless looping.\nEach item has `text` and `isClone` (for aria-hidden on duplicates).\n",
|
|
82353
82329
|
"modifierKind": [
|
|
82354
82330
|
124
|
|
@@ -82364,7 +82340,7 @@
|
|
|
82364
82340
|
"indexKey": "",
|
|
82365
82341
|
"optional": false,
|
|
82366
82342
|
"description": "",
|
|
82367
|
-
"line":
|
|
82343
|
+
"line": 114,
|
|
82368
82344
|
"rawdescription": "\n",
|
|
82369
82345
|
"modifierKind": [
|
|
82370
82346
|
124
|
|
@@ -82379,7 +82355,7 @@
|
|
|
82379
82355
|
"optional": false,
|
|
82380
82356
|
"returnType": "void",
|
|
82381
82357
|
"typeParameters": [],
|
|
82382
|
-
"line":
|
|
82358
|
+
"line": 177,
|
|
82383
82359
|
"deprecated": false,
|
|
82384
82360
|
"deprecationMessage": "",
|
|
82385
82361
|
"rawdescription": "\n",
|
|
@@ -82392,7 +82368,7 @@
|
|
|
82392
82368
|
"optional": false,
|
|
82393
82369
|
"returnType": "void",
|
|
82394
82370
|
"typeParameters": [],
|
|
82395
|
-
"line":
|
|
82371
|
+
"line": 192,
|
|
82396
82372
|
"deprecated": false,
|
|
82397
82373
|
"deprecationMessage": "",
|
|
82398
82374
|
"rawdescription": "\n",
|
|
@@ -82414,7 +82390,7 @@
|
|
|
82414
82390
|
"optional": false,
|
|
82415
82391
|
"returnType": "void",
|
|
82416
82392
|
"typeParameters": [],
|
|
82417
|
-
"line":
|
|
82393
|
+
"line": 203,
|
|
82418
82394
|
"deprecated": false,
|
|
82419
82395
|
"deprecationMessage": "",
|
|
82420
82396
|
"rawdescription": "\nCalled by the emblaChange event from the directive.",
|
|
@@ -82443,7 +82419,7 @@
|
|
|
82443
82419
|
"optional": false,
|
|
82444
82420
|
"returnType": "void",
|
|
82445
82421
|
"typeParameters": [],
|
|
82446
|
-
"line":
|
|
82422
|
+
"line": 226,
|
|
82447
82423
|
"deprecated": false,
|
|
82448
82424
|
"deprecationMessage": "",
|
|
82449
82425
|
"rawdescription": "\nPause the auto-scroll (pause button click).",
|
|
@@ -82459,7 +82435,7 @@
|
|
|
82459
82435
|
"optional": false,
|
|
82460
82436
|
"returnType": "void",
|
|
82461
82437
|
"typeParameters": [],
|
|
82462
|
-
"line":
|
|
82438
|
+
"line": 219,
|
|
82463
82439
|
"deprecated": false,
|
|
82464
82440
|
"deprecationMessage": "",
|
|
82465
82441
|
"rawdescription": "\nStart the auto-scroll (play button click).",
|
|
@@ -82526,23 +82502,23 @@
|
|
|
82526
82502
|
"type": "directive"
|
|
82527
82503
|
}
|
|
82528
82504
|
],
|
|
82529
|
-
"description": "<p>ECL Slogan Ticker component – renders a continuously auto-scrolling horizontal\nticker of slogan items. Supports play/pause controls, RTL layouts, and pauses\nwhen off-screen via IntersectionObserver.</p>\n<p>Uses Embla Carousel (via embla-carousel-angular) with the auto-scroll plugin\nto match the ECL vanilla implementation.</p>\n<p>The component
|
|
82530
|
-
"rawdescription": "\n\nECL Slogan Ticker component – renders a continuously auto-scrolling horizontal\nticker of slogan items. Supports play/pause controls, RTL layouts, and pauses\nwhen off-screen via IntersectionObserver.\n\nUses Embla Carousel (via embla-carousel-angular) with the auto-scroll plugin\nto match the ECL vanilla implementation.\n\nThe component
|
|
82505
|
+
"description": "<p>ECL Slogan Ticker component – renders a continuously auto-scrolling horizontal\nticker of slogan items. Supports play/pause controls, RTL layouts, and pauses\nwhen off-screen via IntersectionObserver.</p>\n<p>Uses Embla Carousel (via embla-carousel-angular) with the auto-scroll plugin\nto match the ECL vanilla implementation.</p>\n<h2>Why two lists (hidden source + generated track)</h2>\n<p>The consumer provides a plain, semantic list:</p>\n<b>Example :</b><div><pre class=\"line-numbers\"><code class=\"language-html\"><ecl-slogan-ticker>\n <ul eclSloganTickerList>\n <li eclSloganTickerSlide>SLOGAN ONE</li>\n <li eclSloganTickerSlide>SLOGAN TWO</li>\n </ul>\n</ecl-slogan-ticker></code></pre></div><p>This authored <code><ul eclSloganTickerList></code> is NOT the list you see scrolling.\n<code>EclSloganTickerListDirective</code> hides it with <code>display: none</code>, and it stays in\nthe light DOM as the single accessible source of truth. The component reads\nthe slide texts out of it via a <code>contentChildren</code> query (see <code>slides</code>) and\nbuilds a SEPARATE, component-owned <code><ul class="ecl-slogan-ticker__track"></code>\nfrom the <code>trackSlides</code> signal. That generated track is what Embla animates.</p>\n<p>Reasons for the split (rather than just scrolling the authored <code><ul></code>):</p>\n<ul>\n<li>Seamless infinite loop needs more content than the author gives. The track\nmust be several times wider than the viewport or the loop visibly jumps, so\n<code>ensureEnoughContent()</code> measures the originals and appends CLONE sets until\nthe total width reaches <code>CLONE_MULTIPLIER</code> × the container width.</li>\n<li>Clones must not pollute accessibility. The authored list stays the one\naccessible list; every generated clone is rendered with <code>aria-hidden</code> so a\nscreen reader announces each slogan once, not once per clone.</li>\n<li>You cannot meaningfully grow the authored <code><ul></code>. Its <code><li></code> children are\nAngular-projected content that Angular owns; imperatively appending clone\n<code><li></code> nodes into it fights change detection. Building our own track signal\nand letting <code>@for</code> render it is clean and safe. <code><li></code> is also only valid\ninside a list element, so the <code><ul eclSloganTickerList></code> wrapper keeps the\nconsumer's authored markup valid and self-contained.</li>\n</ul>\n<p>This mirrors the vanilla ECL slogan ticker (hidden source list + measured,\ncloned, auto-scrolling track).</p>\n<h2>Testing note</h2>\n<p>The visible track is only built after a real width measurement\n(<code>offsetWidth</code>). Under jsdom every element reports a width of 0, so\n<code>ensureEnoughContent()</code> bails early and the generated track stays empty. Unit\ntests should therefore assert slide-driven behavior through its observable\neffects (e.g. the play/pause controls appearing) rather than through the\ngenerated track DOM.</p>\n",
|
|
82506
|
+
"rawdescription": "\n\nECL Slogan Ticker component – renders a continuously auto-scrolling horizontal\nticker of slogan items. Supports play/pause controls, RTL layouts, and pauses\nwhen off-screen via IntersectionObserver.\n\nUses Embla Carousel (via embla-carousel-angular) with the auto-scroll plugin\nto match the ECL vanilla implementation.\n\n## Why two lists (hidden source + generated track)\n\nThe consumer provides a plain, semantic list:\n\n```html\n<ecl-slogan-ticker>\n <ul eclSloganTickerList>\n <li eclSloganTickerSlide>SLOGAN ONE</li>\n <li eclSloganTickerSlide>SLOGAN TWO</li>\n </ul>\n</ecl-slogan-ticker>\n```\n\nThis authored `<ul eclSloganTickerList>` is NOT the list you see scrolling.\n`EclSloganTickerListDirective` hides it with `display: none`, and it stays in\nthe light DOM as the single accessible source of truth. The component reads\nthe slide texts out of it via a `contentChildren` query (see `slides`) and\nbuilds a SEPARATE, component-owned `<ul class=\"ecl-slogan-ticker__track\">`\nfrom the `trackSlides` signal. That generated track is what Embla animates.\n\nReasons for the split (rather than just scrolling the authored `<ul>`):\n\n- Seamless infinite loop needs more content than the author gives. The track\n must be several times wider than the viewport or the loop visibly jumps, so\n `ensureEnoughContent()` measures the originals and appends CLONE sets until\n the total width reaches `CLONE_MULTIPLIER` × the container width.\n- Clones must not pollute accessibility. The authored list stays the one\n accessible list; every generated clone is rendered with `aria-hidden` so a\n screen reader announces each slogan once, not once per clone.\n- You cannot meaningfully grow the authored `<ul>`. Its `<li>` children are\n Angular-projected content that Angular owns; imperatively appending clone\n `<li>` nodes into it fights change detection. Building our own track signal\n and letting `@for` render it is clean and safe. `<li>` is also only valid\n inside a list element, so the `<ul eclSloganTickerList>` wrapper keeps the\n consumer's authored markup valid and self-contained.\n\nThis mirrors the vanilla ECL slogan ticker (hidden source list + measured,\ncloned, auto-scrolling track).\n\n## Testing note\n\nThe visible track is only built after a real width measurement\n(`offsetWidth`). Under jsdom every element reports a width of 0, so\n`ensureEnoughContent()` bails early and the generated track stays empty. Unit\ntests should therefore assert slide-driven behavior through its observable\neffects (e.g. the play/pause controls appearing) rather than through the\ngenerated track DOM.\n",
|
|
82531
82507
|
"type": "component",
|
|
82532
|
-
"sourceCode": "import {\n AfterViewInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n contentChildren,\n effect,\n ElementRef,\n inject,\n input,\n OnDestroy,\n PLATFORM_ID,\n signal,\n viewChild,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { EUI_ECL_ICON } from '@eui/ecl/components/ecl-icon';\nimport { ECLBaseDirective, EclRtlService } from '@eui/ecl/core';\nimport { TranslatePipe } from '@ngx-translate/core';\nimport { EmblaCarouselDirective } from 'embla-carousel-angular';\nimport { EmblaCarouselType, EmblaEventType, EmblaPluginType } from 'embla-carousel';\nimport AutoScroll from 'embla-carousel-auto-scroll';\nimport { Subject, takeUntil } from 'rxjs';\nimport { EclSloganTickerSlideDirective } from './ecl-slogan-ticker-slide.directive';\n\n/**\n * ECL Slogan Ticker component – renders a continuously auto-scrolling horizontal\n * ticker of slogan items. Supports play/pause controls, RTL layouts, and pauses\n * when off-screen via IntersectionObserver.\n *\n * Uses Embla Carousel (via embla-carousel-angular) with the auto-scroll plugin\n * to match the ECL vanilla implementation.\n *\n * The component clones projected slides internally to ensure the track is wide\n * enough for seamless infinite scrolling (matching the vanilla ECL approach).\n */\n@Component({\n selector: 'ecl-slogan-ticker',\n templateUrl: './ecl-slogan-ticker.component.html',\n host: {\n '[class]': 'cssClasses',\n },\n imports: [\n TranslatePipe,\n ...EUI_ECL_ICON,\n EmblaCarouselDirective,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class EclSloganTickerComponent extends ECLBaseDirective implements AfterViewInit, OnDestroy {\n\n /** Aria-label for the ticker region. */\n eclAriaLabel = input<string>('Slogan ticker');\n\n /** Embla carousel options. */\n protected emblaOptions: { loop: boolean; align: 'start'; dragFree: boolean; direction: 'ltr' | 'rtl' } = {\n loop: true,\n align: 'start',\n dragFree: true,\n direction: 'ltr',\n };\n\n /** Embla plugins array (auto-scroll). */\n protected emblaPlugins: EmblaPluginType[] = [];\n\n protected emblaRef = viewChild(EmblaCarouselDirective);\n protected playButton = viewChild<ElementRef>('playButton');\n protected pauseButton = viewChild<ElementRef>('pauseButton');\n protected viewportElement = viewChild<ElementRef>('viewport');\n private trackElement = viewChild<ElementRef>('track');\n\n /** Whether auto-scroll is currently playing. */\n protected isPlaying = signal(false);\n\n /** Whether the user explicitly paused. */\n protected isPaused = signal(false);\n\n /** Whether play/pause controls should be visible. */\n protected showControls = signal(false);\n\n /**\n * Array of slide texts used to render the track.\n * Contains the original slides plus clones for seamless looping.\n * Each item has `text` and `isClone` (for aria-hidden on duplicates).\n */\n protected trackSlides = signal<{ text: string; isClone: boolean }[]>([]);\n\n /** Subscribed Embla events. */\n protected subscribeToEvents: EmblaEventType[] = ['init', 'reInit'];\n\n private slides = contentChildren(EclSloganTickerSlideDirective, { descendants: true });\n private cd = inject(ChangeDetectorRef);\n private elementRef = inject(ElementRef);\n private eclRtlService = inject(EclRtlService);\n private platformId = inject(PLATFORM_ID);\n private destroy$ = new Subject<void>();\n private emblaApi: EmblaCarouselType | undefined;\n private visibilityObserver: IntersectionObserver | null = null;\n private readonly autoplay = true;\n private readonly autoScrollSpeed = 0.9;\n private readonly CLONE_MULTIPLIER = 3;\n\n protected get cssClasses(): string {\n return [super.getCssClasses('ecl-slogan-ticker')].join(' ').trim();\n }\n\n constructor() {\n super();\n\n // React to slides changes\n effect(() => {\n this.showControls.set(this.slides().length >= 1);\n });\n\n // React to RTL changes\n this.eclRtlService.currentRtlState$.pipe(takeUntil(this.destroy$)).subscribe((isRtl) => {\n const direction = isRtl ? 'rtl' : 'ltr';\n if (this.emblaOptions.direction !== direction) {\n this.emblaOptions = { ...this.emblaOptions, direction };\n this.buildPlugins(isRtl);\n this.cd.detectChanges();\n }\n });\n }\n\n ngAfterViewInit(): void {\n if (!isPlatformBrowser(this.platformId)) {\n return;\n }\n\n this.buildPlugins();\n this.cd.detectChanges();\n\n setTimeout(() => {\n this.ensureEnoughContent();\n this.initEmbla();\n this.setupVisibilityObserver();\n });\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n\n if (this.visibilityObserver) {\n this.visibilityObserver.disconnect();\n this.visibilityObserver = null;\n }\n }\n\n /** Called by the emblaChange event from the directive. */\n protected onEmblaChange(eventName: string): void {\n if (!this.emblaApi) {\n this.emblaApi = this.emblaRef()?.emblaApi;\n }\n if (!this.emblaApi) {\n return;\n }\n\n if (eventName === 'init' || eventName === 'reInit') {\n if (this.autoplay && !this.isPaused()) {\n this.startAutoScroll();\n }\n }\n }\n\n /** Start the auto-scroll (play button click). */\n protected onPlayClick(): void {\n this.isPaused.set(false);\n this.startAutoScroll();\n this.pauseButton()?.nativeElement?.focus();\n }\n\n /** Pause the auto-scroll (pause button click). */\n protected onPauseClick(): void {\n this.isPaused.set(true);\n this.stopAutoScroll();\n this.playButton()?.nativeElement?.focus();\n }\n\n /** Start auto-scroll and update state. */\n private startAutoScroll(): void {\n this.isPlaying.set(true);\n const autoScroll = this.emblaApi?.plugins()?.autoScroll;\n (autoScroll as any)?.play();\n this.cd.detectChanges();\n }\n\n /** Stop auto-scroll and update state. */\n private stopAutoScroll(): void {\n this.isPlaying.set(false);\n const autoScroll = this.emblaApi?.plugins()?.autoScroll;\n (autoScroll as any)?.stop();\n this.cd.detectChanges();\n }\n\n /**\n * Clone slides until the track width exceeds 3× container width.\n * Mirrors the vanilla ECL `ensureEnoughContent()` logic.\n */\n private ensureEnoughContent(): void {\n const slides = this.slides();\n if (!slides || slides.length === 0) {\n this.trackSlides.set([]);\n return;\n }\n\n const containerWidth = this.elementRef.nativeElement.offsetWidth;\n if (containerWidth === 0) {\n return;\n }\n\n const originals = slides.map((slide) => ({\n text: slide.el.nativeElement.textContent?.trim() || '',\n isClone: false,\n }));\n\n // Render originals first to measure individual slide widths\n this.trackSlides.set(originals);\n this.cd.detectChanges();\n\n const track = this.trackElement()?.nativeElement;\n if (!track) {\n return;\n }\n\n // Measure each rendered slide's width\n const renderedSlides = track.querySelectorAll('.ecl-slogan-ticker__slide');\n const slideWidths: number[] = [];\n let totalWidth = 0;\n renderedSlides.forEach((el: Element) => {\n const width = (el as HTMLElement).offsetWidth;\n slideWidths.push(width);\n totalWidth += width;\n });\n\n if (totalWidth === 0) {\n return;\n }\n\n // Add clones in full sets until total width >= required width\n const requiredWidth = containerWidth * this.CLONE_MULTIPLIER;\n const allSlides = [...originals];\n\n while (totalWidth < requiredWidth) {\n for (let i = 0; i < originals.length; i++) {\n allSlides.push({ ...originals[i], isClone: true });\n totalWidth += slideWidths[i];\n }\n }\n\n this.trackSlides.set(allSlides);\n this.cd.detectChanges();\n }\n\n /** Build the auto-scroll plugin with current settings. */\n private buildPlugins(isRtl = this.emblaOptions.direction === 'rtl'): void {\n this.emblaPlugins = [\n AutoScroll({\n speed: this.autoScrollSpeed,\n startDelay: 0,\n stopOnInteraction: false,\n playOnInit: this.autoplay,\n direction: isRtl ? 'backward' : 'forward',\n }) as unknown as EmblaPluginType,\n ];\n }\n\n /** Initialize Embla API reference. */\n private initEmbla(): void {\n const embla = this.emblaRef();\n if (!embla) {\n return;\n }\n\n this.emblaApi = embla.emblaApi;\n\n if (!this.emblaApi) {\n return;\n }\n\n if (this.autoplay && !this.isPaused()) {\n this.startAutoScroll();\n }\n\n this.cd.detectChanges();\n }\n\n /**\n * Observe visibility to pause auto-scroll when the component is off-screen\n * and resume when it comes back into view.\n */\n private setupVisibilityObserver(): void {\n this.visibilityObserver = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n if (!this.isPlaying() && !this.isPaused()) {\n this.startAutoScroll();\n }\n } else if (this.isPlaying()) {\n this.stopAutoScroll();\n }\n },\n { threshold: 0 },\n );\n\n this.visibilityObserver.observe(this.elementRef.nativeElement);\n }\n}\n",
|
|
82508
|
+
"sourceCode": "import {\n AfterViewInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n contentChildren,\n effect,\n ElementRef,\n inject,\n input,\n OnDestroy,\n PLATFORM_ID,\n signal,\n viewChild,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { EUI_ECL_ICON } from '@eui/ecl/components/ecl-icon';\nimport { ECLBaseDirective, EclRtlService } from '@eui/ecl/core';\nimport { TranslatePipe } from '@ngx-translate/core';\nimport { EmblaCarouselDirective } from 'embla-carousel-angular';\nimport { EmblaCarouselType, EmblaEventType, EmblaPluginType } from 'embla-carousel';\nimport AutoScroll from 'embla-carousel-auto-scroll';\nimport { Subject, takeUntil } from 'rxjs';\nimport { EclSloganTickerSlideDirective } from './ecl-slogan-ticker-slide.directive';\n\n/**\n * ECL Slogan Ticker component – renders a continuously auto-scrolling horizontal\n * ticker of slogan items. Supports play/pause controls, RTL layouts, and pauses\n * when off-screen via IntersectionObserver.\n *\n * Uses Embla Carousel (via embla-carousel-angular) with the auto-scroll plugin\n * to match the ECL vanilla implementation.\n *\n * ## Why two lists (hidden source + generated track)\n *\n * The consumer provides a plain, semantic list:\n *\n * ```html\n * <ecl-slogan-ticker>\n * <ul eclSloganTickerList>\n * <li eclSloganTickerSlide>SLOGAN ONE</li>\n * <li eclSloganTickerSlide>SLOGAN TWO</li>\n * </ul>\n * </ecl-slogan-ticker>\n * ```\n *\n * This authored `<ul eclSloganTickerList>` is NOT the list you see scrolling.\n * `EclSloganTickerListDirective` hides it with `display: none`, and it stays in\n * the light DOM as the single accessible source of truth. The component reads\n * the slide texts out of it via a `contentChildren` query (see `slides`) and\n * builds a SEPARATE, component-owned `<ul class=\"ecl-slogan-ticker__track\">`\n * from the `trackSlides` signal. That generated track is what Embla animates.\n *\n * Reasons for the split (rather than just scrolling the authored `<ul>`):\n *\n * - Seamless infinite loop needs more content than the author gives. The track\n * must be several times wider than the viewport or the loop visibly jumps, so\n * `ensureEnoughContent()` measures the originals and appends CLONE sets until\n * the total width reaches `CLONE_MULTIPLIER` × the container width.\n * - Clones must not pollute accessibility. The authored list stays the one\n * accessible list; every generated clone is rendered with `aria-hidden` so a\n * screen reader announces each slogan once, not once per clone.\n * - You cannot meaningfully grow the authored `<ul>`. Its `<li>` children are\n * Angular-projected content that Angular owns; imperatively appending clone\n * `<li>` nodes into it fights change detection. Building our own track signal\n * and letting `@for` render it is clean and safe. `<li>` is also only valid\n * inside a list element, so the `<ul eclSloganTickerList>` wrapper keeps the\n * consumer's authored markup valid and self-contained.\n *\n * This mirrors the vanilla ECL slogan ticker (hidden source list + measured,\n * cloned, auto-scrolling track).\n *\n * ## Testing note\n *\n * The visible track is only built after a real width measurement\n * (`offsetWidth`). Under jsdom every element reports a width of 0, so\n * `ensureEnoughContent()` bails early and the generated track stays empty. Unit\n * tests should therefore assert slide-driven behavior through its observable\n * effects (e.g. the play/pause controls appearing) rather than through the\n * generated track DOM.\n */\n@Component({\n selector: 'ecl-slogan-ticker',\n templateUrl: './ecl-slogan-ticker.component.html',\n host: {\n '[class]': 'cssClasses',\n },\n imports: [\n TranslatePipe,\n ...EUI_ECL_ICON,\n EmblaCarouselDirective,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class EclSloganTickerComponent extends ECLBaseDirective implements AfterViewInit, OnDestroy {\n\n /** Aria-label for the ticker region. */\n eclAriaLabel = input<string>('Slogan ticker');\n\n /** Embla carousel options. */\n protected emblaOptions: { loop: boolean; align: 'start'; dragFree: boolean; direction: 'ltr' | 'rtl' } = {\n loop: true,\n align: 'start',\n dragFree: true,\n direction: 'ltr',\n };\n\n /** Embla plugins array (auto-scroll). */\n protected emblaPlugins: EmblaPluginType[] = [];\n\n protected emblaRef = viewChild(EmblaCarouselDirective);\n protected playButton = viewChild<ElementRef>('playButton');\n protected pauseButton = viewChild<ElementRef>('pauseButton');\n protected viewportElement = viewChild<ElementRef>('viewport');\n private trackElement = viewChild<ElementRef>('track');\n\n /** Whether auto-scroll is currently playing. */\n protected isPlaying = signal(false);\n\n /** Whether the user explicitly paused. */\n protected isPaused = signal(false);\n\n /** Whether play/pause controls should be visible. */\n protected showControls = signal(false);\n\n /**\n * Array of slide texts used to render the track.\n * Contains the original slides plus clones for seamless looping.\n * Each item has `text` and `isClone` (for aria-hidden on duplicates).\n */\n protected trackSlides = signal<{ text: string; isClone: boolean }[]>([]);\n\n /** Subscribed Embla events. */\n protected subscribeToEvents: EmblaEventType[] = ['init', 'reInit'];\n\n /**\n * Slides read from the consumer's hidden `<ul eclSloganTickerList>` source\n * list. This is the DATA source only; the visible track is a separate,\n * component-generated list built from `trackSlides`. See the class-level\n * doc for why the authored list and the rendered track are kept separate.\n */\n private slides = contentChildren(EclSloganTickerSlideDirective, { descendants: true });\n private cd = inject(ChangeDetectorRef);\n private elementRef = inject(ElementRef);\n private eclRtlService = inject(EclRtlService);\n private platformId = inject(PLATFORM_ID);\n private destroy$ = new Subject<void>();\n private emblaApi: EmblaCarouselType | undefined;\n private visibilityObserver: IntersectionObserver | null = null;\n private readonly autoplay = true;\n private readonly autoScrollSpeed = 0.9;\n private readonly CLONE_MULTIPLIER = 3;\n\n protected get cssClasses(): string {\n return [super.getCssClasses('ecl-slogan-ticker')].join(' ').trim();\n }\n\n constructor() {\n super();\n\n // React to slides changes\n effect(() => {\n this.showControls.set(this.slides().length >= 1);\n });\n\n // React to RTL changes\n this.eclRtlService.currentRtlState$.pipe(takeUntil(this.destroy$)).subscribe((isRtl) => {\n const direction = isRtl ? 'rtl' : 'ltr';\n if (this.emblaOptions.direction !== direction) {\n this.emblaOptions = { ...this.emblaOptions, direction };\n this.buildPlugins(isRtl);\n this.cd.detectChanges();\n }\n });\n }\n\n ngAfterViewInit(): void {\n if (!isPlatformBrowser(this.platformId)) {\n return;\n }\n\n this.buildPlugins();\n this.cd.detectChanges();\n\n setTimeout(() => {\n this.ensureEnoughContent();\n this.initEmbla();\n this.setupVisibilityObserver();\n });\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n\n if (this.visibilityObserver) {\n this.visibilityObserver.disconnect();\n this.visibilityObserver = null;\n }\n }\n\n /** Called by the emblaChange event from the directive. */\n protected onEmblaChange(eventName: string): void {\n if (!this.emblaApi) {\n this.emblaApi = this.emblaRef()?.emblaApi;\n }\n if (!this.emblaApi) {\n return;\n }\n\n if (eventName === 'init' || eventName === 'reInit') {\n if (this.autoplay && !this.isPaused()) {\n this.startAutoScroll();\n }\n }\n }\n\n /** Start the auto-scroll (play button click). */\n protected onPlayClick(): void {\n this.isPaused.set(false);\n this.startAutoScroll();\n this.pauseButton()?.nativeElement?.focus();\n }\n\n /** Pause the auto-scroll (pause button click). */\n protected onPauseClick(): void {\n this.isPaused.set(true);\n this.stopAutoScroll();\n this.playButton()?.nativeElement?.focus();\n }\n\n /** Start auto-scroll and update state. */\n private startAutoScroll(): void {\n this.isPlaying.set(true);\n const autoScroll = this.emblaApi?.plugins()?.autoScroll;\n (autoScroll as any)?.play();\n this.cd.detectChanges();\n }\n\n /** Stop auto-scroll and update state. */\n private stopAutoScroll(): void {\n this.isPlaying.set(false);\n const autoScroll = this.emblaApi?.plugins()?.autoScroll;\n (autoScroll as any)?.stop();\n this.cd.detectChanges();\n }\n\n /**\n * Clone slides until the track width exceeds 3× container width.\n * Mirrors the vanilla ECL `ensureEnoughContent()` logic.\n */\n private ensureEnoughContent(): void {\n const slides = this.slides();\n if (!slides || slides.length === 0) {\n this.trackSlides.set([]);\n return;\n }\n\n const containerWidth = this.elementRef.nativeElement.offsetWidth;\n if (containerWidth === 0) {\n return;\n }\n\n const originals = slides.map((slide) => ({\n text: slide.el.nativeElement.textContent?.trim() || '',\n isClone: false,\n }));\n\n // Render originals first to measure individual slide widths\n this.trackSlides.set(originals);\n this.cd.detectChanges();\n\n const track = this.trackElement()?.nativeElement;\n if (!track) {\n return;\n }\n\n // Measure each rendered slide's width\n const renderedSlides = track.querySelectorAll('.ecl-slogan-ticker__slide');\n const slideWidths: number[] = [];\n let totalWidth = 0;\n renderedSlides.forEach((el: Element) => {\n const width = (el as HTMLElement).offsetWidth;\n slideWidths.push(width);\n totalWidth += width;\n });\n\n if (totalWidth === 0) {\n return;\n }\n\n // Add clones in full sets until total width >= required width\n const requiredWidth = containerWidth * this.CLONE_MULTIPLIER;\n const allSlides = [...originals];\n\n while (totalWidth < requiredWidth) {\n for (let i = 0; i < originals.length; i++) {\n allSlides.push({ ...originals[i], isClone: true });\n totalWidth += slideWidths[i];\n }\n }\n\n this.trackSlides.set(allSlides);\n this.cd.detectChanges();\n }\n\n /** Build the auto-scroll plugin with current settings. */\n private buildPlugins(isRtl = this.emblaOptions.direction === 'rtl'): void {\n this.emblaPlugins = [\n AutoScroll({\n speed: this.autoScrollSpeed,\n startDelay: 0,\n stopOnInteraction: false,\n playOnInit: this.autoplay,\n direction: isRtl ? 'backward' : 'forward',\n }) as unknown as EmblaPluginType,\n ];\n }\n\n /** Initialize Embla API reference. */\n private initEmbla(): void {\n const embla = this.emblaRef();\n if (!embla) {\n return;\n }\n\n this.emblaApi = embla.emblaApi;\n\n if (!this.emblaApi) {\n return;\n }\n\n if (this.autoplay && !this.isPaused()) {\n this.startAutoScroll();\n }\n\n this.cd.detectChanges();\n }\n\n /**\n * Observe visibility to pause auto-scroll when the component is off-screen\n * and resume when it comes back into view.\n */\n private setupVisibilityObserver(): void {\n this.visibilityObserver = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n if (!this.isPlaying() && !this.isPaused()) {\n this.startAutoScroll();\n }\n } else if (this.isPlaying()) {\n this.stopAutoScroll();\n }\n },\n { threshold: 0 },\n );\n\n this.visibilityObserver.observe(this.elementRef.nativeElement);\n }\n}\n",
|
|
82533
82509
|
"assetsDirs": [],
|
|
82534
82510
|
"styleUrlsData": "",
|
|
82535
82511
|
"stylesData": "",
|
|
82536
82512
|
"jsdoctags": [
|
|
82537
82513
|
{
|
|
82538
82514
|
"pos": 814,
|
|
82539
|
-
"end":
|
|
82515
|
+
"end": 3556,
|
|
82540
82516
|
"kind": 321,
|
|
82541
82517
|
"id": 0,
|
|
82542
82518
|
"flags": 16777216,
|
|
82543
82519
|
"modifierFlagsCache": 0,
|
|
82544
82520
|
"transformFlags": 0,
|
|
82545
|
-
"comment": "ECL Slogan Ticker component – renders a continuously auto-scrolling horizontal\nticker of slogan items. Supports play/pause controls, RTL layouts, and pauses\nwhen off-screen via IntersectionObserver.\n\nUses Embla Carousel (via embla-carousel-angular) with the auto-scroll plugin\nto match the ECL vanilla implementation.\n\nThe component
|
|
82521
|
+
"comment": "ECL Slogan Ticker component – renders a continuously auto-scrolling horizontal\nticker of slogan items. Supports play/pause controls, RTL layouts, and pauses\nwhen off-screen via IntersectionObserver.\n\nUses Embla Carousel (via embla-carousel-angular) with the auto-scroll plugin\nto match the ECL vanilla implementation.\n\n## Why two lists (hidden source + generated track)\n\nThe consumer provides a plain, semantic list:\n\n```html\n<ecl-slogan-ticker>\n <ul eclSloganTickerList>\n <li eclSloganTickerSlide>SLOGAN ONE</li>\n <li eclSloganTickerSlide>SLOGAN TWO</li>\n </ul>\n</ecl-slogan-ticker>\n```\n\nThis authored `<ul eclSloganTickerList>` is NOT the list you see scrolling.\n`EclSloganTickerListDirective` hides it with `display: none`, and it stays in\nthe light DOM as the single accessible source of truth. The component reads\nthe slide texts out of it via a `contentChildren` query (see `slides`) and\nbuilds a SEPARATE, component-owned `<ul class=\"ecl-slogan-ticker__track\">`\nfrom the `trackSlides` signal. That generated track is what Embla animates.\n\nReasons for the split (rather than just scrolling the authored `<ul>`):\n\n- Seamless infinite loop needs more content than the author gives. The track\n must be several times wider than the viewport or the loop visibly jumps, so\n `ensureEnoughContent()` measures the originals and appends CLONE sets until\n the total width reaches `CLONE_MULTIPLIER` × the container width.\n- Clones must not pollute accessibility. The authored list stays the one\n accessible list; every generated clone is rendered with `aria-hidden` so a\n screen reader announces each slogan once, not once per clone.\n- You cannot meaningfully grow the authored `<ul>`. Its `<li>` children are\n Angular-projected content that Angular owns; imperatively appending clone\n `<li>` nodes into it fights change detection. Building our own track signal\n and letting `@for` render it is clean and safe. `<li>` is also only valid\n inside a list element, so the `<ul eclSloganTickerList>` wrapper keeps the\n consumer's authored markup valid and self-contained.\n\nThis mirrors the vanilla ECL slogan ticker (hidden source list + measured,\ncloned, auto-scrolling track).\n\n## Testing note\n\nThe visible track is only built after a real width measurement\n(`offsetWidth`). Under jsdom every element reports a width of 0, so\n`ensureEnoughContent()` bails early and the generated track stays empty. Unit\ntests should therefore assert slide-driven behavior through its observable\neffects (e.g. the play/pause controls appearing) rather than through the\ngenerated track DOM."
|
|
82546
82522
|
}
|
|
82547
82523
|
],
|
|
82548
82524
|
"constructorObj": {
|
|
@@ -82551,7 +82527,7 @@
|
|
|
82551
82527
|
"deprecated": false,
|
|
82552
82528
|
"deprecationMessage": "",
|
|
82553
82529
|
"args": [],
|
|
82554
|
-
"line":
|
|
82530
|
+
"line": 156,
|
|
82555
82531
|
"rawdescription": "\n"
|
|
82556
82532
|
},
|
|
82557
82533
|
"extends": [
|
|
@@ -82568,7 +82544,7 @@
|
|
|
82568
82544
|
"name": "cssClasses",
|
|
82569
82545
|
"type": "string",
|
|
82570
82546
|
"returnType": "string",
|
|
82571
|
-
"line":
|
|
82547
|
+
"line": 154,
|
|
82572
82548
|
"rawdescription": "\n",
|
|
82573
82549
|
"description": ""
|
|
82574
82550
|
}
|
|
@@ -82822,7 +82798,7 @@
|
|
|
82822
82798
|
},
|
|
82823
82799
|
{
|
|
82824
82800
|
"name": "EclSocialMediaFollowItemComponent",
|
|
82825
|
-
"id": "component-EclSocialMediaFollowItemComponent-
|
|
82801
|
+
"id": "component-EclSocialMediaFollowItemComponent-4d681eab78f3c19c56ea7257583c64f115fd7e7d71c472b1e4283066c6a140e35ac096a345d68e7097d14833840a5ade158853c6012a4edad152b38642f45176",
|
|
82826
82802
|
"file": "packages/ecl/components/ecl-social-media-follow/ecl-social-media-follow-item.component.ts",
|
|
82827
82803
|
"encapsulation": [],
|
|
82828
82804
|
"entryComponents": [],
|
|
@@ -82904,10 +82880,10 @@
|
|
|
82904
82880
|
{
|
|
82905
82881
|
"name": "isInverted",
|
|
82906
82882
|
"coverageIgnore": false,
|
|
82907
|
-
"defaultValue": "
|
|
82883
|
+
"defaultValue": "false",
|
|
82908
82884
|
"deprecated": false,
|
|
82909
82885
|
"deprecationMessage": "",
|
|
82910
|
-
"type": "
|
|
82886
|
+
"type": "boolean",
|
|
82911
82887
|
"indexKey": "",
|
|
82912
82888
|
"optional": false,
|
|
82913
82889
|
"description": "",
|
|
@@ -83133,7 +83109,7 @@
|
|
|
83133
83109
|
"description": "",
|
|
83134
83110
|
"rawdescription": "\n",
|
|
83135
83111
|
"type": "component",
|
|
83136
|
-
"sourceCode": "import { booleanAttribute, Component, inject, input, output } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { EclIconSet, EUI_ECL_ICON } from '@eui/ecl/components/ecl-icon';\nimport { EclBaseEvent } from '@eui/ecl/core';\nimport { ECLBaseDirective } from '@eui/ecl/core';\nimport { EUI_ECL_LINK } from '@eui/ecl/components/ecl-link';\nimport { RouterLink, RouterLinkWithHref } from '@angular/router';\nimport { EclSocialMediaFollowComponent } from './ecl-social-media-follow.component';\n\nexport class EclSocialMediaFollowItemClickEvent extends EclBaseEvent {}\n\n@Component({\n selector: 'ecl-social-media-follow-item',\n templateUrl: './ecl-social-media-follow-item.component.html',\n imports: [...EUI_ECL_ICON, ...EUI_ECL_LINK, NgTemplateOutlet, RouterLink, RouterLinkWithHref],\n host: {\n role: 'listitem',\n '[class]': 'cssClasses',\n },\n})\nexport class EclSocialMediaFollowItemComponent extends ECLBaseDirective {\n readonly isInverted = input
|
|
83112
|
+
"sourceCode": "import { booleanAttribute, Component, inject, input, output } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { EclIconSet, EUI_ECL_ICON } from '@eui/ecl/components/ecl-icon';\nimport { EclBaseEvent } from '@eui/ecl/core';\nimport { ECLBaseDirective } from '@eui/ecl/core';\nimport { EUI_ECL_LINK } from '@eui/ecl/components/ecl-link';\nimport { RouterLink, RouterLinkWithHref } from '@angular/router';\nimport { EclSocialMediaFollowComponent } from './ecl-social-media-follow.component';\n\nexport class EclSocialMediaFollowItemClickEvent extends EclBaseEvent {}\n\n@Component({\n selector: 'ecl-social-media-follow-item',\n templateUrl: './ecl-social-media-follow-item.component.html',\n imports: [...EUI_ECL_ICON, ...EUI_ECL_LINK, NgTemplateOutlet, RouterLink, RouterLinkWithHref],\n host: {\n role: 'listitem',\n '[class]': 'cssClasses',\n },\n})\nexport class EclSocialMediaFollowItemComponent extends ECLBaseDirective {\n readonly isInverted = input(false, { transform: booleanAttribute });\n icon = input<string>();\n iconSet = input<EclIconSet>('social-media');\n readonly href = input('');\n readonly routerLink = input('');\n readonly target = input<string>(undefined);\n readonly size = input('m');\n readonly extraAttributes = input<{\n name: string;\n value?: string | null;\n }[]>(undefined);\n\n get cssClasses(): string {\n return [super.getCssClasses('ecl-social-media-follow__item')].join(' ').trim();\n }\n\n readonly itemClick = output<EclSocialMediaFollowItemClickEvent>();\n\n private parent = inject(EclSocialMediaFollowComponent);\n\n get isIconOnly(): boolean {\n return this.parent?.isIconOnly() ?? false;\n }\n\n onItemClick(evt: Event): void {\n this.itemClick.emit(new EclSocialMediaFollowItemClickEvent());\n if (!this.href() && !this.routerLink()) {\n evt.preventDefault();\n }\n }\n}\n",
|
|
83137
83113
|
"assetsDirs": [],
|
|
83138
83114
|
"styleUrlsData": "",
|
|
83139
83115
|
"stylesData": "",
|
|
@@ -90332,7 +90308,7 @@
|
|
|
90332
90308
|
"name": "ARROW_SIZE",
|
|
90333
90309
|
"ctype": "miscellaneous",
|
|
90334
90310
|
"subtype": "variable",
|
|
90335
|
-
"file": "packages/ecl/components/ecl-site-header/
|
|
90311
|
+
"file": "packages/ecl/components/ecl-site-header/login/ecl-site-header-login.component.ts",
|
|
90336
90312
|
"coverageIgnore": false,
|
|
90337
90313
|
"deprecated": false,
|
|
90338
90314
|
"deprecationMessage": "",
|
|
@@ -90345,7 +90321,7 @@
|
|
|
90345
90321
|
"name": "ARROW_SIZE",
|
|
90346
90322
|
"ctype": "miscellaneous",
|
|
90347
90323
|
"subtype": "variable",
|
|
90348
|
-
"file": "packages/ecl/components/ecl-site-header/
|
|
90324
|
+
"file": "packages/ecl/components/ecl-site-header/search/ecl-site-header-search.component.ts",
|
|
90349
90325
|
"coverageIgnore": false,
|
|
90350
90326
|
"deprecated": false,
|
|
90351
90327
|
"deprecationMessage": "",
|
|
@@ -91301,7 +91277,7 @@
|
|
|
91301
91277
|
"name": "RESIZE_DEBOUNCE",
|
|
91302
91278
|
"ctype": "miscellaneous",
|
|
91303
91279
|
"subtype": "variable",
|
|
91304
|
-
"file": "packages/ecl/components/ecl-site-header/
|
|
91280
|
+
"file": "packages/ecl/components/ecl-site-header/login/ecl-site-header-login.component.ts",
|
|
91305
91281
|
"coverageIgnore": false,
|
|
91306
91282
|
"deprecated": false,
|
|
91307
91283
|
"deprecationMessage": "",
|
|
@@ -91314,7 +91290,7 @@
|
|
|
91314
91290
|
"name": "RESIZE_DEBOUNCE",
|
|
91315
91291
|
"ctype": "miscellaneous",
|
|
91316
91292
|
"subtype": "variable",
|
|
91317
|
-
"file": "packages/ecl/components/ecl-site-header/
|
|
91293
|
+
"file": "packages/ecl/components/ecl-site-header/search/ecl-site-header-search.component.ts",
|
|
91318
91294
|
"coverageIgnore": false,
|
|
91319
91295
|
"deprecated": false,
|
|
91320
91296
|
"deprecationMessage": "",
|
|
@@ -91817,12 +91793,12 @@
|
|
|
91817
91793
|
"description": "<p>Half the arrow's own width, matching ECL's <code>arrowSize</code>.</p>\n"
|
|
91818
91794
|
}
|
|
91819
91795
|
],
|
|
91820
|
-
"packages/ecl/components/ecl-site-header/
|
|
91796
|
+
"packages/ecl/components/ecl-site-header/login/ecl-site-header-login.component.ts": [
|
|
91821
91797
|
{
|
|
91822
91798
|
"name": "ARROW_SIZE",
|
|
91823
91799
|
"ctype": "miscellaneous",
|
|
91824
91800
|
"subtype": "variable",
|
|
91825
|
-
"file": "packages/ecl/components/ecl-site-header/
|
|
91801
|
+
"file": "packages/ecl/components/ecl-site-header/login/ecl-site-header-login.component.ts",
|
|
91826
91802
|
"coverageIgnore": false,
|
|
91827
91803
|
"deprecated": false,
|
|
91828
91804
|
"deprecationMessage": "",
|
|
@@ -91832,60 +91808,60 @@
|
|
|
91832
91808
|
"description": "<p>Half the arrow's own width, matching ECL's <code>arrowSize</code>.</p>\n"
|
|
91833
91809
|
},
|
|
91834
91810
|
{
|
|
91835
|
-
"name": "
|
|
91811
|
+
"name": "RESIZE_DEBOUNCE",
|
|
91836
91812
|
"ctype": "miscellaneous",
|
|
91837
91813
|
"subtype": "variable",
|
|
91838
|
-
"file": "packages/ecl/components/ecl-site-header/
|
|
91814
|
+
"file": "packages/ecl/components/ecl-site-header/login/ecl-site-header-login.component.ts",
|
|
91839
91815
|
"coverageIgnore": false,
|
|
91840
91816
|
"deprecated": false,
|
|
91841
91817
|
"deprecationMessage": "",
|
|
91842
|
-
"type": "
|
|
91843
|
-
"defaultValue": "
|
|
91844
|
-
|
|
91818
|
+
"type": "number",
|
|
91819
|
+
"defaultValue": "200",
|
|
91820
|
+
"rawdescription": "Debounce for the resize recomputation, matching ECL's `handleResize`.",
|
|
91821
|
+
"description": "<p>Debounce for the resize recomputation, matching ECL's <code>handleResize</code>.</p>\n"
|
|
91822
|
+
}
|
|
91823
|
+
],
|
|
91824
|
+
"packages/ecl/components/ecl-site-header/search/ecl-site-header-search.component.ts": [
|
|
91845
91825
|
{
|
|
91846
|
-
"name": "
|
|
91826
|
+
"name": "ARROW_SIZE",
|
|
91847
91827
|
"ctype": "miscellaneous",
|
|
91848
91828
|
"subtype": "variable",
|
|
91849
91829
|
"file": "packages/ecl/components/ecl-site-header/search/ecl-site-header-search.component.ts",
|
|
91850
91830
|
"coverageIgnore": false,
|
|
91851
91831
|
"deprecated": false,
|
|
91852
91832
|
"deprecationMessage": "",
|
|
91853
|
-
"type": "
|
|
91854
|
-
"defaultValue": "
|
|
91833
|
+
"type": "string",
|
|
91834
|
+
"defaultValue": "'0.5rem'",
|
|
91835
|
+
"rawdescription": "Half the arrow's own width, matching ECL's `arrowSize`.",
|
|
91836
|
+
"description": "<p>Half the arrow's own width, matching ECL's <code>arrowSize</code>.</p>\n"
|
|
91855
91837
|
},
|
|
91856
91838
|
{
|
|
91857
|
-
"name": "
|
|
91839
|
+
"name": "ECL_SITE_HEADER_SEARCH_VALUE_ACCESSOR",
|
|
91858
91840
|
"ctype": "miscellaneous",
|
|
91859
91841
|
"subtype": "variable",
|
|
91860
91842
|
"file": "packages/ecl/components/ecl-site-header/search/ecl-site-header-search.component.ts",
|
|
91861
91843
|
"coverageIgnore": false,
|
|
91862
91844
|
"deprecated": false,
|
|
91863
91845
|
"deprecationMessage": "",
|
|
91864
|
-
"type": "
|
|
91865
|
-
"defaultValue": "
|
|
91866
|
-
|
|
91867
|
-
"description": "<p>Debounce for the resize recomputation, matching ECL's <code>handleResize</code>.</p>\n"
|
|
91868
|
-
}
|
|
91869
|
-
],
|
|
91870
|
-
"packages/ecl/components/ecl-site-header/login/ecl-site-header-login.component.ts": [
|
|
91846
|
+
"type": "Provider",
|
|
91847
|
+
"defaultValue": "{\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => EclSiteHeaderSearchComponent),\n multi: true,\n}"
|
|
91848
|
+
},
|
|
91871
91849
|
{
|
|
91872
|
-
"name": "
|
|
91850
|
+
"name": "noop",
|
|
91873
91851
|
"ctype": "miscellaneous",
|
|
91874
91852
|
"subtype": "variable",
|
|
91875
|
-
"file": "packages/ecl/components/ecl-site-header/
|
|
91853
|
+
"file": "packages/ecl/components/ecl-site-header/search/ecl-site-header-search.component.ts",
|
|
91876
91854
|
"coverageIgnore": false,
|
|
91877
91855
|
"deprecated": false,
|
|
91878
91856
|
"deprecationMessage": "",
|
|
91879
|
-
"type": "
|
|
91880
|
-
"defaultValue": "
|
|
91881
|
-
"rawdescription": "Half the arrow's own width, matching ECL's `arrowSize`.",
|
|
91882
|
-
"description": "<p>Half the arrow's own width, matching ECL's <code>arrowSize</code>.</p>\n"
|
|
91857
|
+
"type": "unknown",
|
|
91858
|
+
"defaultValue": "(): void => {\n /** empty*/\n}"
|
|
91883
91859
|
},
|
|
91884
91860
|
{
|
|
91885
91861
|
"name": "RESIZE_DEBOUNCE",
|
|
91886
91862
|
"ctype": "miscellaneous",
|
|
91887
91863
|
"subtype": "variable",
|
|
91888
|
-
"file": "packages/ecl/components/ecl-site-header/
|
|
91864
|
+
"file": "packages/ecl/components/ecl-site-header/search/ecl-site-header-search.component.ts",
|
|
91889
91865
|
"coverageIgnore": false,
|
|
91890
91866
|
"deprecated": false,
|
|
91891
91867
|
"deprecationMessage": "",
|
package/docs/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# eui documentation
|
|
2
2
|
|
|
3
|
-
> Generated by compodoc 2.0.0 at 2026-10-
|
|
3
|
+
> Generated by compodoc 2.0.0 at 2026-10-02T08:07:19.312Z, llm-md export.
|
|
4
4
|
|
|
5
5
|
## Modules
|
|
6
6
|
|
|
@@ -2724,7 +2724,7 @@ File: `packages/ecl/components/ecl-slogan-ticker/ecl-slogan-ticker.component.ts`
|
|
|
2724
2724
|
Selector: `ecl-slogan-ticker`
|
|
2725
2725
|
Change detection: `ChangeDetectionStrategy.OnPush`
|
|
2726
2726
|
|
|
2727
|
-
Description: ECL Slogan Ticker component – renders a continuously auto-scrolling horizontal ticker of slogan items. Supports play/pause controls, RTL layouts, and pauses when off-screen via IntersectionObserver. Uses Embla Carousel (via embla-carousel-angular) with the auto-scroll plugin to match the ECL vanilla implementation. The component
|
|
2727
|
+
Description: ECL Slogan Ticker component – renders a continuously auto-scrolling horizontal ticker of slogan items. Supports play/pause controls, RTL layouts, and pauses when off-screen via IntersectionObserver. Uses Embla Carousel (via embla-carousel-angular) with the auto-scroll plugin to match the ECL vanilla implementation. Why two lists (hidden source + generated track) The consumer provides a plain, semantic list: Example : \<ecl-slogan-ticker\> \<ul eclSloganTickerList\> \<li eclSloganTickerSlide\>SLOGAN ONE\</li\> \<li eclSloganTickerSlide\>SLOGAN TWO\</li\> \</ul\> \</ecl-slogan-ticker\> This authored \<ul eclSloganTickerList\> is NOT the list you see scrolling. EclSloganTickerListDirective hides it with display: none , and it stays in the light DOM as the single accessible source of truth. The component reads the slide texts out of it via a contentChildren query (see slides ) and builds a SEPARATE, component-owned \<ul class="ecl-slogan-ticker\_\_track"\> from the trackSlides signal. That generated track is what Embla animates. Reasons for the split (rather than just scrolling the authored \<ul\> ): Seamless infinite loop needs more content than the author gives. The track must be several times wider than the viewport or the loop visibly jumps, so ensureEnoughContent() measures the originals and appends CLONE sets until the total width reaches CLONE\_MULTIPLIER × the container width. Clones must not pollute accessibility. The authored list stays the one accessible list; every generated clone is rendered with aria-hidden so a screen reader announces each slogan once, not once per clone. You cannot meaningfully grow the authored \<ul\> . Its \<li\> children are Angular-projected content that Angular owns; imperatively appending clone \<li\> nodes into it fights change detection. Building our own track signal and letting @for render it is clean and safe. \<li\> is also only valid inside a list element, so the \<ul eclSloganTickerList\> wrapper keeps the consumer's authored markup valid and self-contained. This mirrors the vanilla ECL slogan ticker (hidden source list + measured, cloned, auto-scrolling track). Testing note The visible track is only built after a real width measurement ( offsetWidth ). Under jsdom every element reports a width of 0, so ensureEnoughContent() bails early and the generated track stays empty. Unit tests should therefore assert slide-driven behavior through its observable effects (e.g. the play/pause controls appearing) rather than through the generated track DOM.
|
|
2728
2728
|
|
|
2729
2729
|
Inputs:
|
|
2730
2730
|
- `eclAriaLabel: string = 'Slogan ticker'` — Aria-label for the ticker region.
|
|
@@ -2762,7 +2762,7 @@ Inputs:
|
|
|
2762
2762
|
- `href: string = ''`
|
|
2763
2763
|
- `icon: string`
|
|
2764
2764
|
- `iconSet: EclIconSet = 'social-media'`
|
|
2765
|
-
- `isInverted:
|
|
2765
|
+
- `isInverted: boolean = false`
|
|
2766
2766
|
- `routerLink: string = ''`
|
|
2767
2767
|
- `size: string = 'm'`
|
|
2768
2768
|
- `target: string = undefined`
|