@salesforcedevs/dx-components 1.3.57-alpha.9 → 1.3.62-alpha01

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.
Files changed (51) hide show
  1. package/package.json +5 -3
  2. package/src/assets/icons/salesforcebrand-sprite/svg/symbols.svg +118 -7
  3. package/src/assets/svg/privacyicon.png +0 -0
  4. package/src/modules/dx/cardBlogPost/cardBlogPost.html +3 -3
  5. package/src/modules/dx/cardBlogPost/cardBlogPost.ts +18 -3
  6. package/src/modules/dx/cardCallout/cardCallout.ts +7 -0
  7. package/src/modules/dx/cardContent/cardContent.css +5 -0
  8. package/src/modules/dx/cardContent/cardContent.html +12 -6
  9. package/src/modules/dx/cardContent/cardContent.ts +39 -7
  10. package/src/modules/dx/cardDocs/cardDocs.html +6 -2
  11. package/src/modules/dx/cardDocs/cardDocs.ts +47 -0
  12. package/src/modules/dx/cardExpanded/cardExpanded.css +15 -5
  13. package/src/modules/dx/cardExpanded/cardExpanded.html +9 -5
  14. package/src/modules/dx/cardExpanded/cardExpanded.ts +24 -8
  15. package/src/modules/dx/cardNews/cardNews.ts +1 -0
  16. package/src/modules/dx/cardTrial/cardTrial.html +5 -1
  17. package/src/modules/dx/cardTrial/cardTrial.ts +40 -1
  18. package/src/modules/dx/cardTrialExpanded/cardTrialExpanded.css +10 -0
  19. package/src/modules/dx/cardTrialExpanded/cardTrialExpanded.html +16 -10
  20. package/src/modules/dx/cardTrialExpanded/cardTrialExpanded.ts +43 -1
  21. package/src/modules/dx/codeBlock/codeBlock.ts +19 -4
  22. package/src/modules/dx/dropdown/dropdown.html +2 -4
  23. package/src/modules/dx/dropdown/dropdown.ts +2 -7
  24. package/src/modules/dx/dropdownOption/dropdownOption.ts +11 -21
  25. package/src/modules/dx/feature/feature.html +1 -0
  26. package/src/modules/dx/feature/feature.ts +13 -0
  27. package/src/modules/dx/featuredContentHeader/featuredContentHeader.ts +116 -2
  28. package/src/modules/dx/footer/footer.css +9 -0
  29. package/src/modules/dx/footer/footer.html +6 -0
  30. package/src/modules/dx/footer/footer.ts +7 -14
  31. package/src/modules/dx/footer/links.ts +5 -0
  32. package/src/modules/dx/footerOption/footerOption.ts +12 -4
  33. package/src/modules/dx/groupText/groupText.ts +8 -14
  34. package/src/modules/dx/headerMobileNavMenuOption/headerMobileNavMenuOption.ts +8 -0
  35. package/src/modules/dx/headerNav/headerNav.html +1 -0
  36. package/src/modules/dx/headerNav/headerNav.ts +4 -0
  37. package/src/modules/dx/mainContentHeader/mainContentHeader.html +2 -2
  38. package/src/modules/dx/mainContentHeader/mainContentHeader.ts +21 -6
  39. package/src/modules/dx/modal/modal.html +1 -1
  40. package/src/modules/dx/modal/modal.ts +6 -1
  41. package/src/modules/dx/pagination/pagination.ts +5 -10
  42. package/src/modules/dx/scrollManager/scrollManager.ts +14 -9
  43. package/src/modules/dx/searchResults/coveo.css +32 -31
  44. package/src/modules/dx/searchResults/searchResults.ts +11 -0
  45. package/src/modules/dx/tab/tab.ts +14 -3
  46. package/src/modules/dx/tabPanelItem/tabPanelItem.html +1 -1
  47. package/src/modules/dx/tbidAvatarButton/tbidAvatarButton.ts +26 -16
  48. package/src/modules/dx/toc/toc.ts +24 -7
  49. package/src/modules/dx/treeItem/treeItem.ts +24 -7
  50. package/src/modules/dxBaseElements/headerBase/headerBase.ts +10 -8
  51. package/src/modules/dxUtils/normalizers/normalizers.ts +1 -3
@@ -1,21 +1,41 @@
1
1
  import { api } from "lwc";
2
2
  import { ArchiveCard } from "dxBaseElements/archiveCard";
3
3
  import cx from "classnames";
4
+ import ImageAndLabel from "../imageAndLabel";
5
+ import { toJson } from "dxUtils/normalizers";
4
6
 
5
7
  export default class ExpandedCard extends ArchiveCard {
6
8
  @api body!: string;
7
9
  @api date: string | null = null;
8
10
  @api topics: Array<string> = [];
9
- @api authorLabel?: string | null = null;
10
- @api authorImgSrc?: string | null = null;
11
11
  @api length?: string;
12
+ @api
13
+ get authors() {
14
+ if (this._authors && this._authors.length) {
15
+ return this._authors!.map((author, index) => ({
16
+ ...author,
17
+ imgSrc: !this.mobile ? author.image_src : "",
18
+ key: index
19
+ }));
20
+ }
21
+ return undefined;
22
+ }
23
+
24
+ set authors(value: any) {
25
+ if (value !== "undefined") {
26
+ this._authors = (toJson(value) as Array<any>)?.filter(
27
+ (author) => author.name
28
+ );
29
+ }
30
+ }
31
+ private _authors?: Array<ImageAndLabel>;
12
32
 
13
33
  private get isBlog(): boolean {
14
34
  return this.contentType === "blog";
15
35
  }
16
36
 
17
37
  private get hasAuthor(): boolean {
18
- return !!(this.authorImgSrc && this.authorLabel);
38
+ return this.authors?.length;
19
39
  }
20
40
 
21
41
  private get renderAuthor(): boolean {
@@ -27,17 +47,13 @@ export default class ExpandedCard extends ArchiveCard {
27
47
  }
28
48
 
29
49
  private get renderDot(): boolean {
30
- return !!((this.renderAuthor || this.renderLength) && this.date);
50
+ return !!(this.renderLength && this.date);
31
51
  }
32
52
 
33
53
  private get hasTopics(): boolean {
34
54
  return !!this.topics?.length;
35
55
  }
36
56
 
37
- private get authorImage(): string {
38
- return !this.mobile && this.authorImgSrc ? this.authorImgSrc : "";
39
- }
40
-
41
57
  private get containerClass(): string {
42
58
  return cx("card-container", this.mobile && "card-mobile");
43
59
  }
@@ -74,5 +74,6 @@ export default class CardNews extends LightningElement {
74
74
  clickUrl: this.href
75
75
  };
76
76
  track(e.currentTarget!, "custEv_ctaLinkClick", payload);
77
+ track(e.currentTarget!, "custEv_linkClick", payload);
77
78
  }
78
79
  }
@@ -3,6 +3,7 @@
3
3
  if:true={hasTwoButtons}
4
4
  open={_modalOpen}
5
5
  ontogglemodal={toggleModal}
6
+ onclosemodal={closeModal}
6
7
  >
7
8
  <dx-card-trial-expanded
8
9
  badge-background-color={badgeBackgroundColor}
@@ -21,7 +22,8 @@
21
22
  target={target}
22
23
  terms={terms}
23
24
  title={title}
24
- ontogglemodal={toggleModal}
25
+ type-badge-value={modalTypeBadgeValue}
26
+ type-badge-dark={modalTypeBadgeDark}
25
27
  ></dx-card-trial-expanded>
26
28
  </dx-modal>
27
29
  <div class="recommended-container" if:true={recommended}>
@@ -69,6 +71,7 @@
69
71
  size="large"
70
72
  href={buttonOneHref}
71
73
  variant="primary"
74
+ target={buttonOneTarget}
72
75
  font="sans"
73
76
  icon-symbol={buttonOneIcon}
74
77
  icon-size={buttonOneIconSize}
@@ -82,6 +85,7 @@
82
85
  href={buttonOneHref}
83
86
  variant="primary"
84
87
  font="sans"
88
+ target={buttonOneTarget}
85
89
  icon-symbol={buttonOneIcon}
86
90
  icon-size={buttonOneIconSize}
87
91
  >
@@ -1,6 +1,7 @@
1
1
  import { LightningElement, api } from "lwc";
2
2
  import cx from "classnames";
3
3
  import { toJson } from "dxUtils/normalizers";
4
+ import { track } from "dxUtils/analytics";
4
5
 
5
6
  export default class CardTrial extends LightningElement {
6
7
  @api badgeBackgroundColor?: string = "indigo-vibrant-90";
@@ -34,6 +35,8 @@ export default class CardTrial extends LightningElement {
34
35
  @api typeBadgeColor?: string;
35
36
  @api typeBadgeSize?: string = "default";
36
37
  @api typeBadgeDark?: boolean = false;
38
+ @api modalTypeBadgeValue?: string;
39
+ @api modalTypeBadgeDark?: boolean;
37
40
 
38
41
  @api
39
42
  get details() {
@@ -71,6 +74,10 @@ export default class CardTrial extends LightningElement {
71
74
  return this.hasButtons ? "menu" : "link";
72
75
  }
73
76
 
77
+ get buttonOneTarget() {
78
+ return this.isExternalURL(this.buttonOneHref) ? "_blank" : null;
79
+ }
80
+
74
81
  private _details!: string[];
75
82
  private _modalDetails!: [{ title: string; subtitle: string }];
76
83
  private _modalOpen = false;
@@ -93,10 +100,42 @@ export default class CardTrial extends LightningElement {
93
100
  this._modalOpen = !this._modalOpen;
94
101
  }
95
102
 
96
- private handleClick() {
103
+ private closeModal() {
104
+ this._modalOpen = false;
105
+ }
106
+
107
+ private handleClick(e: PointerEvent) {
97
108
  // card is clickable only if it doesn't contain buttons (for accessibility reasons)
98
109
  if (!this.hasButtons) {
99
110
  window.location.assign(this.href);
111
+ if (this.href.includes("signup")) {
112
+ this.handleSignUpClick(e);
113
+ }
114
+ }
115
+ }
116
+
117
+ private handleSignUpClick(e: PointerEvent) {
118
+ const payload = {
119
+ click_text: this.label,
120
+ element_title: "dx-button",
121
+ element_type: "card",
122
+ click_url: this.href,
123
+ content_category: "cta"
124
+ };
125
+ track(e.currentTarget!, "custEv_signupStart", payload);
126
+ track(e.currentTarget!, "custEv_linkClick", payload);
127
+ }
128
+
129
+ private isExternalURL(url: string | undefined): boolean {
130
+ if (!url) {
131
+ return false;
132
+ }
133
+
134
+ try {
135
+ const value = new URL(url);
136
+ return value.host !== window.location.host;
137
+ } catch (e) {
138
+ return false;
100
139
  }
101
140
  }
102
141
  }
@@ -19,6 +19,11 @@
19
19
  pointer-events: all;
20
20
  }
21
21
 
22
+ .badge-container {
23
+ display: flex;
24
+ justify-content: space-between;
25
+ }
26
+
22
27
  .badge-main {
23
28
  --dx-c-icon-badge-icon-size: var(--dx-g-spacing-mlg);
24
29
  --dx-c-icon-badge-size: var(--dx-g-spacing-2xl);
@@ -138,6 +143,11 @@ dx-button {
138
143
  color: white;
139
144
  }
140
145
 
146
+ .bullet-list .bullet {
147
+ padding-left: 1.39571429em;
148
+ text-indent: -1.39571429em;
149
+ }
150
+
141
151
  @media screen and (max-width: 768px) {
142
152
  .container {
143
153
  max-width: 550px;
@@ -10,14 +10,21 @@
10
10
  Close
11
11
  </dx-button>
12
12
  </div>
13
- <div class="content-container">
13
+ <div class="content-container" tabindex="0" onkeydown={handleKeyDown}>
14
14
  <div class="col-one">
15
- <dx-icon-badge
16
- class="badge-main"
17
- background-color={badgeBackgroundColor}
18
- symbol={badgeSymbol}
19
- sprite={badgeSprite}
20
- ></dx-icon-badge>
15
+ <div class="badge-container">
16
+ <dx-icon-badge
17
+ class="badge-main"
18
+ background-color={badgeBackgroundColor}
19
+ symbol={badgeSymbol}
20
+ sprite={badgeSprite}
21
+ ></dx-icon-badge>
22
+ <dx-type-badge
23
+ if:true={typeBadgeValue}
24
+ value={typeBadgeValue}
25
+ dark={typeBadgeDark}
26
+ ></dx-type-badge>
27
+ </div>
21
28
  <dx-card-title
22
29
  class="title-main"
23
30
  href={href}
@@ -31,6 +38,7 @@
31
38
  class="button-cta"
32
39
  size="large"
33
40
  href={href}
41
+ onclick={handleSignUpClick}
34
42
  variant="primary"
35
43
  font="sans"
36
44
  icon-symbol={buttonIcon}
@@ -72,9 +80,7 @@
72
80
  for:each={colTwoItem.bullets}
73
81
  for:item="bullet"
74
82
  >
75
- <li key={colTwoItem.bullet} class="bullet">
76
- {bullet}
77
- </li>
83
+ <li key={bullet} class="bullet">{bullet}</li>
78
84
  </template>
79
85
  </ul>
80
86
  </div>
@@ -1,5 +1,6 @@
1
1
  import { LightningElement, api } from "lwc";
2
2
  import { toJson } from "dxUtils/normalizers";
3
+ import { track } from "dxUtils/analytics";
3
4
 
4
5
  export default class CardTrial extends LightningElement {
5
6
  @api badgeBackgroundColor?: string = "indigo-vibrant-90";
@@ -16,6 +17,8 @@ export default class CardTrial extends LightningElement {
16
17
  @api target?: string;
17
18
  @api terms!: string;
18
19
  @api title!: string;
20
+ @api typeBadgeValue?: string;
21
+ @api typeBadgeDark?: boolean = false;
19
22
 
20
23
  @api
21
24
  get details() {
@@ -37,6 +40,13 @@ export default class CardTrial extends LightningElement {
37
40
  private _colTwoDetails!: string[];
38
41
  private stickyCloseObserver!: IntersectionObserver;
39
42
 
43
+ connectedCallback(): void {
44
+ this.template.addEventListener(
45
+ "keydown",
46
+ this.handleKeyDown.bind(this)
47
+ );
48
+ }
49
+
40
50
  renderedCallback(): void {
41
51
  this.observeStickyClose();
42
52
  }
@@ -61,7 +71,39 @@ export default class CardTrial extends LightningElement {
61
71
  }
62
72
  }
63
73
 
74
+ private handleKeyDown(event: KeyboardEvent): void {
75
+ if (event.key === "Escape") {
76
+ this.handleModalClose();
77
+ }
78
+ }
79
+
80
+ private handleModalToggle() {
81
+ this.dispatchEvent(
82
+ new CustomEvent("togglemodal", {
83
+ bubbles: true
84
+ })
85
+ );
86
+ }
87
+
64
88
  private handleModalClose() {
65
- this.dispatchEvent(new CustomEvent("togglemodal"));
89
+ this.dispatchEvent(
90
+ new CustomEvent("closemodal", {
91
+ bubbles: true
92
+ })
93
+ );
94
+ }
95
+
96
+ private handleSignUpClick(e: PointerEvent) {
97
+ if (this.href.includes("signup")) {
98
+ const payload = {
99
+ click_text: this.buttonCta,
100
+ element_title: "dx-button",
101
+ element_type: "card",
102
+ click_url: this.href,
103
+ content_category: "cta"
104
+ };
105
+ track(e.currentTarget!, "custEv_signupStart", payload);
106
+ track(e.currentTarget!, "custEv_linkClick", payload);
107
+ }
66
108
  }
67
109
  }
@@ -174,6 +174,15 @@ export default class CodeBlock extends LightningElement {
174
174
  this.selectedLanguageLabel = this.selectedLanguage.label;
175
175
  this.selectedLanguageId = this.selectedLanguage.id;
176
176
  this.formatCodeBlock();
177
+
178
+ gtmTrack(newLang, "custEv_ctaLinkClick", {
179
+ event: "custEv_ctaLinkClick",
180
+ click_text: newLang.detail,
181
+ element_title: "dx-dropdown",
182
+ click_url: this.selectedLanguageId,
183
+ element_type: "link",
184
+ content_category: "code block"
185
+ });
177
186
  }
178
187
 
179
188
  /**
@@ -181,8 +190,11 @@ export default class CodeBlock extends LightningElement {
181
190
  * https://github.com/storybookjs/storybook/issues/13529
182
191
  */
183
192
  async copySource(event: any) {
184
- gtmTrack(event.target, "custEv_codeBlock", {
185
- codeBlockAction: "copy"
193
+ gtmTrack(event.target, "custEv_iconClick", {
194
+ click_text: this.copyBtnText,
195
+ element_type: "icon",
196
+ element_title: "dx-icon",
197
+ content_category: "code block"
186
198
  });
187
199
 
188
200
  try {
@@ -206,8 +218,11 @@ export default class CodeBlock extends LightningElement {
206
218
  this.theme = this.theme === DARK ? LIGHT : DARK;
207
219
  setLocalStorageData(LOCAL_STORAGE_KEY, this.theme);
208
220
 
209
- gtmTrack(event.target, "custEv_codeBlock", {
210
- codeBlockAction: this.theme === DARK ? "darkmode" : "lightmode"
221
+ gtmTrack(event.target, "custEv_iconClick", {
222
+ click_text: this.updateThemeBtnText,
223
+ element_type: "icon",
224
+ element_title: "dx-icon",
225
+ content_category: "code block"
211
226
  });
212
227
  }
213
228
 
@@ -21,9 +21,8 @@
21
21
  >
22
22
  <template for:each={options} for:item="option" for:index="index">
23
23
  <dx-dropdown-option
24
- suppress-gtm-nav-headings={suppressGtmNavHeadings}
25
24
  analytics-event={analyticsEvent}
26
- analytics-base-payload={analyticsBasePayload}
25
+ analytics-payload={analyticsPayload}
27
26
  active={option.active}
28
27
  key-value={option.keyValue}
29
28
  if:false={option.options}
@@ -44,9 +43,8 @@
44
43
  <span class="menu_heading">{option.label}</span>
45
44
  <template for:each={option.options} for:item="suboption">
46
45
  <dx-dropdown-option
47
- suppress-gtm-nav-headings={suppressGTMNavHeadings}
48
46
  analytics-event={analyticsEvent}
49
- analytics-base-payload={analyticsBasePayload}
47
+ analytics-payload={analyticsPayload}
50
48
  active={suboption.active}
51
49
  key={suboption.id}
52
50
  key-value={suboption.keyValue}
@@ -38,13 +38,8 @@ export default class Dropdown extends LightningElement {
38
38
  @api variant: DropdownVariant = "base";
39
39
 
40
40
  // props forwarded to dropdown option
41
- @api analyticsEvent: string | null = null;
42
- @api analyticsBasePayload: AnalyticsPayload = {
43
- elementType: "dropdown",
44
- destinationType: "internal",
45
- ctaClick: true
46
- };
47
- @api suppressGtmNavHeadings?: boolean = false;
41
+ @api analyticsEvent?: string;
42
+ @api analyticsPayload?: AnalyticsPayload;
48
43
 
49
44
  @api
50
45
  get options() {
@@ -9,12 +9,11 @@ import { track } from "dxUtils/analytics";
9
9
 
10
10
  export default class DropdownOption extends LightningElement {
11
11
  @api option!: OptionWithNested;
12
- @api suppressGtmNavHeadings: boolean = false;
13
12
  @api keyValue!: string;
14
13
  @api active: boolean = false;
15
14
  @api navItemLabel: String | null = null;
16
15
  @api analyticsEvent: string | null = null;
17
- @api analyticsBasePayload!: AnalyticsPayload;
16
+ @api analyticsPayload?: AnalyticsPayload | undefined;
18
17
  @api indexPosition: number = 0;
19
18
  @api variant: DropdownVariant = "base";
20
19
 
@@ -43,25 +42,16 @@ export default class DropdownOption extends LightningElement {
43
42
  new CustomEvent("select", { detail: this.keyValue })
44
43
  );
45
44
 
46
- const payload: any = {
47
- ...this.analyticsBasePayload,
48
- clickText: this.keyValue || undefined,
49
- itemTitle: this.option.label || undefined,
50
- clickUrl: this.option.link?.href || undefined
51
- };
52
-
53
- const navHeading = this.navItemLabel || this.option.label || undefined;
54
- const navSubHeading =
55
- (this.navItemLabel ? this.option.label : undefined) || undefined;
56
-
57
- if (!this.suppressGtmNavHeadings) {
58
- payload.navHeading = navHeading;
59
- payload.navSubHeading = navSubHeading;
60
- payload.pageLocation = window.location.pathname;
61
- }
62
-
63
- if (this.analyticsEvent && e.currentTarget) {
64
- track(e.currentTarget, this.analyticsEvent, payload);
45
+ if (this.analyticsEvent && this.analyticsPayload && e.currentTarget) {
46
+ track(e.currentTarget, this.analyticsEvent, {
47
+ ...this.analyticsPayload,
48
+ click_text: this.option.label,
49
+ click_url: this.option.link?.href,
50
+ element_type: this.analyticsPayload?.element_type || "dropdown",
51
+ ...(this.analyticsPayload?.nav_type
52
+ ? { nav_item: this.option.label }
53
+ : {})
54
+ });
65
55
  }
66
56
  }
67
57
  }
@@ -12,6 +12,7 @@
12
12
  target={target}
13
13
  icon-symbol={iconSymbol}
14
14
  href={buttonHref}
15
+ onclick={handleClick}
15
16
  >
16
17
  {buttonLabel}
17
18
  </dx-button>
@@ -1,4 +1,5 @@
1
1
  import { LightningElement, api } from "lwc";
2
+ import { track } from "dxUtils/analytics";
2
3
 
3
4
  export default class Feature extends LightningElement {
4
5
  @api label!: string;
@@ -31,4 +32,16 @@ export default class Feature extends LightningElement {
31
32
  get iconSymbol(): string {
32
33
  return this.isExternalLink ? "new_window" : "";
33
34
  }
35
+
36
+ handleClick(event: Event) {
37
+ const payload = {
38
+ click_text: this.buttonLabel,
39
+ element_title: "dx-button",
40
+ click_url: this.buttonHref,
41
+ element_type: "button",
42
+ content_category: "cta"
43
+ };
44
+ track(event.target!, "custEv_ctaButtonClick", payload);
45
+ track(event.target!, "custEv_linkClick", payload);
46
+ }
34
47
  }
@@ -4,6 +4,7 @@ import { LightningSlotElement } from "typings/custom";
4
4
  import { isSlotEmpty } from "dxUtils/slot";
5
5
  import { toJson } from "dxUtils/normalizers";
6
6
  import { ImageAndLabel } from "../imageAndLabel";
7
+ import { track } from "dxUtils/analytics";
7
8
  import svgs from "./svgs";
8
9
 
9
10
  export default class FeaturedContentHeader extends LightningElement {
@@ -32,14 +33,19 @@ export default class FeaturedContentHeader extends LightningElement {
32
33
  private _authors?: Array<ImageAndLabel>;
33
34
  private isSlotEmpty: boolean = true;
34
35
  private isLinkHovered: boolean = false;
35
-
36
+ private progressPodcastPlayed = {
37
+ percent25: false,
38
+ percent50: false,
39
+ percent75: false,
40
+ percent100: false
41
+ };
36
42
  // ! Move this map away from the component and probably the filter too.
37
43
  @api
38
44
  get authors() {
39
45
  if (this._authors && this._authors.length) {
40
46
  return this._authors!.map((author, index) => ({
41
47
  ...author,
42
- imgSrc: author.avatar_urls?.["24"] || author.imgSrc,
48
+ imgSrc: author.image_src,
43
49
  key: index
44
50
  }));
45
51
  }
@@ -103,6 +109,57 @@ export default class FeaturedContentHeader extends LightningElement {
103
109
 
104
110
  private onSlotChange(e: LightningSlotElement): void {
105
111
  this.isSlotEmpty = isSlotEmpty(e);
112
+ const slot = e.target;
113
+ const slotElements = slot.assignedElements();
114
+
115
+ if (slotElements.length) {
116
+ const slotContentElement = slotElements[0];
117
+ const playerElement = slotContentElement.shadowRoot.querySelector(
118
+ "audio"
119
+ ) as HTMLAudioElement;
120
+ if (playerElement) {
121
+ playerElement.addEventListener("play", (event: Event) => {
122
+ const playerState = this.handlePlayerState(playerElement);
123
+ this.trackAnalytics(
124
+ event,
125
+ "play",
126
+ playerState.timePlayed,
127
+ playerState.percentagePlayed
128
+ );
129
+ });
130
+
131
+ playerElement.addEventListener("pause", (event: Event) => {
132
+ const playerState = this.handlePlayerState(playerElement);
133
+ this.trackAnalytics(
134
+ event,
135
+ "pause",
136
+ playerState.timePlayed,
137
+ playerState.percentagePlayed
138
+ );
139
+ });
140
+
141
+ playerElement.addEventListener("timeupdate", (event: Event) => {
142
+ const playerState = this.handlePlayerState(playerElement);
143
+ if (
144
+ (playerState.percentagePlayed === 25 &&
145
+ !this.progressPodcastPlayed.percent25) ||
146
+ (playerState.percentagePlayed === 50 &&
147
+ !this.progressPodcastPlayed.percent50) ||
148
+ (playerState.percentagePlayed === 75 &&
149
+ !this.progressPodcastPlayed.percent75) ||
150
+ (playerState.percentagePlayed === 100 &&
151
+ !this.progressPodcastPlayed.percent100)
152
+ ) {
153
+ this.trackAnalytics(
154
+ event,
155
+ "progress",
156
+ playerState.timePlayed,
157
+ playerState.percentagePlayed
158
+ );
159
+ }
160
+ });
161
+ }
162
+ }
106
163
  }
107
164
 
108
165
  renderedCallback(): void {
@@ -119,4 +176,61 @@ export default class FeaturedContentHeader extends LightningElement {
119
176
  }
120
177
  }
121
178
  }
179
+
180
+ private trackAnalytics(
181
+ event: Event,
182
+ action: "play" | "pause" | "progress",
183
+ timePlayed: number,
184
+ percentagePlayed: number
185
+ ) {
186
+ let trackEvent = null;
187
+ switch (action) {
188
+ case "play":
189
+ trackEvent = "custEv_podcastPlay";
190
+ break;
191
+ case "pause":
192
+ trackEvent = "custEv_podcastPause";
193
+ break;
194
+ case "progress":
195
+ trackEvent = "custEv_podcastProgress";
196
+ break;
197
+ default:
198
+ trackEvent = null;
199
+ }
200
+
201
+ if (!trackEvent) {
202
+ return;
203
+ }
204
+
205
+ track(event.target!, trackEvent, {
206
+ media_name: this.title,
207
+ media_action: action,
208
+ media_episode: this.href,
209
+ media_percentage_played: percentagePlayed,
210
+ media_seconds_played: timePlayed,
211
+ media_type: "podcast"
212
+ });
213
+
214
+ if (Math.trunc(percentagePlayed) === 25) {
215
+ this.progressPodcastPlayed.percent25 = true;
216
+ } else if (Math.trunc(percentagePlayed) === 50) {
217
+ this.progressPodcastPlayed.percent50 = true;
218
+ } else if (Math.trunc(percentagePlayed) === 75) {
219
+ this.progressPodcastPlayed.percent75 = true;
220
+ } else if (Math.trunc(percentagePlayed) === 100) {
221
+ this.progressPodcastPlayed.percent100 = true;
222
+ }
223
+ }
224
+
225
+ private handlePlayerState(player: HTMLAudioElement) {
226
+ let timePlayed = 0;
227
+ let percentagePlayed = 0;
228
+ if (player) {
229
+ timePlayed = player.currentTime / 1000;
230
+ percentagePlayed = Math.trunc(
231
+ (player.currentTime * 100) / player.duration
232
+ );
233
+ }
234
+ return { timePlayed, percentagePlayed };
235
+ }
122
236
  }
@@ -44,6 +44,11 @@ footer.signup-variant-no-signup {
44
44
  color: var(--dx-g-blue-vibrant-20);
45
45
  }
46
46
 
47
+ .term-icon {
48
+ width: 35px;
49
+ margin: 0 var(--dx-g-spacing-xs);
50
+ }
51
+
47
52
  /* TOP */
48
53
 
49
54
  .content-container_top {
@@ -402,4 +407,8 @@ footer.signup-variant-no-signup {
402
407
  .subscription dx-input {
403
408
  width: 100%;
404
409
  }
410
+
411
+ .term-icon {
412
+ margin-left: 0;
413
+ }
405
414
  }
@@ -139,6 +139,12 @@
139
139
  </template>
140
140
  <template if:false={term.onclick}>
141
141
  <a href={term.href} key={term.label} rel={term.rel}>
142
+ <img
143
+ if:true={term.img}
144
+ class="term-icon"
145
+ src={term.img}
146
+ alt="Footer Term Icon"
147
+ />
142
148
  {term.label}
143
149
  </a>
144
150
  </template>