@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
@@ -14,19 +14,10 @@ import { track } from "dxUtils/analytics";
14
14
  const PATH = "/newsletter";
15
15
  const LOCAL_STORAGE_KEY = "subscriberEmail";
16
16
  const createNewsletterSignupHref = (email: string): string => {
17
- window.localStorage.setItem(
18
- LOCAL_STORAGE_KEY,
19
- email
20
- );
17
+ window.localStorage.setItem(LOCAL_STORAGE_KEY, email);
21
18
  return PATH;
22
- }
23
-
24
- const ANALYTICS_INFO = {
25
- itemTitle: "Newsletter Sign Up Footer",
26
- elementType: "button",
27
- destinationType: "internal",
28
- ctaClick: true
29
19
  };
20
+
30
21
  export default class Footer extends LightningElement {
31
22
  @api locale: string | null = null;
32
23
  @api
@@ -110,9 +101,11 @@ export default class Footer extends LightningElement {
110
101
  const href = createNewsletterSignupHref(e.detail);
111
102
 
112
103
  track(e.currentTarget, "custEv_ctaButtonClick", {
113
- ...ANALYTICS_INFO,
114
- clickText: this.inputSubmitLabel,
115
- clickUrl: PATH
104
+ click_text: this.inputSubmitLabel,
105
+ click_url: PATH,
106
+ element_type: "button",
107
+ element_title: "dx-footer",
108
+ content_category: "cta"
116
109
  });
117
110
 
118
111
  window.location.assign(href);
@@ -167,6 +167,11 @@ export const termsLinks = [
167
167
  rel: "nofollow",
168
168
  label: "Cookie Preferences",
169
169
  onclick: openOneTrustInfoDisplay
170
+ },
171
+ {
172
+ href: "https://www.salesforce.com/form/other/privacy-request/",
173
+ label: "Your Privacy Choices",
174
+ img: "/assets/svg/privacyicon.png"
170
175
  }
171
176
  ];
172
177
 
@@ -10,10 +10,18 @@ export default class FooterOption extends LightningElement {
10
10
 
11
11
  sendGtm(e: PointerEvent) {
12
12
  track(e.currentTarget, "custEv_bottomNavLinkClick", {
13
- navHeading: this.generalLabel || undefined,
14
- navSubHeading: this.label || undefined,
15
- clickText: this.href || undefined,
16
- pageLocation: window.location.pathname || undefined
13
+ click_text: this.label || undefined,
14
+ click_url: this.href || undefined,
15
+ nav_item: this.label || undefined,
16
+ element_type: "link",
17
+ nav_type: "footer"
18
+ });
19
+ track(e.currentTarget!, "custEv_linkClick", {
20
+ click_text: this.label,
21
+ element_title: "dx-button",
22
+ click_url: this.href,
23
+ element_type: "link",
24
+ content_category: "cta"
17
25
  });
18
26
  }
19
27
  }
@@ -20,12 +20,6 @@ const subtitleClasses = {
20
20
  [MEDIUM]: "dx-text-heading-7"
21
21
  };
22
22
 
23
- const ANALYTICS_INFO = {
24
- elementType: "button",
25
- destinationType: "internal",
26
- ctaClick: true
27
- };
28
-
29
23
  export default class GroupText extends LightningElement {
30
24
  @api title!: string;
31
25
  @api body?: string;
@@ -99,13 +93,13 @@ export default class GroupText extends LightningElement {
99
93
  }
100
94
 
101
95
  private trackClick(event: Event) {
102
- const payload = {
103
- ...ANALYTICS_INFO,
104
- clickText: event.currentTarget.innerText,
105
- itemTitle: event.currentTarget.innerText,
106
- clickUrl: event.currentTarget.href
107
- };
108
-
109
- track(event.target!, "custEv_ctaButtonClick", payload);
96
+ track(event.target!, "custEv_ctaButtonClick", {
97
+ click_text: event.currentTarget.innerText,
98
+ item_title: event.currentTarget.innerText,
99
+ click_url: event.currentTarget.href,
100
+ element_type: "button",
101
+ element_title: "dx-group-text",
102
+ content_category: "cta"
103
+ });
110
104
  }
111
105
  }
@@ -16,5 +16,13 @@ export default class HeaderMobileNavMenuOption extends LightningElement {
16
16
  clickText: this.href || undefined,
17
17
  pageLocation: window.location.pathname || undefined
18
18
  });
19
+
20
+ track(e.currentTarget!, "custEv_linkClick", {
21
+ click_text: this.itemLabel,
22
+ element_title: "dx-button",
23
+ click_url: this.href,
24
+ element_type: "link",
25
+ content_category: "cta"
26
+ });
19
27
  }
20
28
  }
@@ -14,6 +14,7 @@
14
14
  <!-- DESKTOP "MORE CONTENT" NAV ITEM -->
15
15
  <dx-dropdown
16
16
  analytics-event="custEv_topNavLinkClick"
17
+ analytics-payload={ANALYTICS_PAYLOAD}
17
18
  if:true={navItem.options}
18
19
  nav-item-label={navItem.label}
19
20
  open-on-hover
@@ -46,6 +46,10 @@ export default class HeaderNav extends LightningElement {
46
46
  }
47
47
 
48
48
  private _navItems: OptionWithNested[] = [];
49
+ private ANALYTICS_PAYLOAD = {
50
+ nav_level: "2",
51
+ nav_type: "global"
52
+ };
49
53
 
50
54
  private requestOpenNavMenu(e: PointerEvent) {
51
55
  const detail = (<HTMLButtonElement>e.currentTarget).getAttribute(
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div class="container">
3
- <div class="text-container" style={style}>
2
+ <div class="container" style={style}>
3
+ <div class="text-container">
4
4
  <h1 class="heading dx-text-heading-2">{title}</h1>
5
5
  <span class="body dx-text-body-1">{body}</span>
6
6
  <span if:true={subtitle} class="subtitle dx-text-heading-4b">
@@ -1,4 +1,5 @@
1
1
  import { LightningElement, api } from "lwc";
2
+ import cx from "classnames";
2
3
  import { track } from "dxUtils/analytics";
3
4
 
4
5
  export default class MainContentHeader extends LightningElement {
@@ -10,16 +11,30 @@ export default class MainContentHeader extends LightningElement {
10
11
  @api imgSrc!: string;
11
12
  @api imgSrcMobile!: string;
12
13
  @api ctaTarget?: string | null = null;
14
+ @api backgroundGradientColor?: string;
15
+
16
+ private get style() {
17
+ return cx(
18
+ this.backgroundGradientColor &&
19
+ `background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #ffff 100%), var(--dx-g-${this.backgroundGradientColor});`
20
+ );
21
+ }
13
22
 
14
23
  private onCtaClick(e: Event) {
15
24
  if (e.currentTarget) {
16
25
  track(e.currentTarget, "custEv_ctaButtonClick", {
17
- clickText: this.ctaLabel,
18
- itemTitle: this.title,
19
- clickUrl: this.ctaHref,
20
- elementType: "button",
21
- destinationType: "internal",
22
- ctaClick: true
26
+ click_text: this.ctaLabel,
27
+ click_url: this.ctaHref,
28
+ element_type: "button",
29
+ element_title: "dx-button",
30
+ content_category: "cta"
31
+ });
32
+ track(e.currentTarget!, "custEv_linkClick", {
33
+ click_text: this.ctaLabel,
34
+ element_title: "dx-button",
35
+ click_url: this.ctaHref,
36
+ element_type: "link",
37
+ content_category: "cta"
23
38
  });
24
39
  }
25
40
  }
@@ -5,6 +5,6 @@
5
5
  <div
6
6
  if:true={open}
7
7
  class="modal-drawer_overlay"
8
- onclick={handleToggleModal}
8
+ onclick={handleCloseModal}
9
9
  ></div>
10
10
  </template>
@@ -39,7 +39,7 @@ export default class Modal extends LightningElement {
39
39
 
40
40
  handleKeyDown(event: KeyboardEvent): void {
41
41
  if (event.key === "Escape") {
42
- this.handleToggleModal();
42
+ this.handleCloseModal();
43
43
  }
44
44
  }
45
45
 
@@ -47,4 +47,9 @@ export default class Modal extends LightningElement {
47
47
  this._open = !this._open;
48
48
  this.dispatchEvent(new CustomEvent("togglemodal"));
49
49
  }
50
+
51
+ handleCloseModal() {
52
+ this._open = false;
53
+ this.dispatchEvent(new CustomEvent("closemodal"));
54
+ }
50
55
  }
@@ -2,13 +2,6 @@ import classNames from "classnames";
2
2
  import { LightningElement, api } from "lwc";
3
3
  import { track } from "dxUtils/analytics";
4
4
 
5
- const ANALYTICS_INFO = {
6
- clickUrl: window.location.href,
7
- ctaClick: false,
8
- elementType: "button",
9
- destinationType: "internal"
10
- };
11
-
12
5
  enum PaginationClickType {
13
6
  Number,
14
7
  Next,
@@ -99,9 +92,11 @@ export default class Pagination extends LightningElement {
99
92
  : (e.target as HTMLElement).dataset.partId;
100
93
 
101
94
  track(e.currentTarget!, "custEv_pagination", {
102
- ...ANALYTICS_INFO,
103
- clickText,
104
- itemTitle: clickText
95
+ click_text: clickText,
96
+ click_url: window.location.href,
97
+ element_type: "button",
98
+ nav_type: "pagination",
99
+ nav_item: clickText
105
100
  });
106
101
 
107
102
  this.dispatchEvent(new CustomEvent("pagechange", { detail: page }));
@@ -1,4 +1,5 @@
1
1
  import { LightningElement } from "lwc";
2
+ import { throttle } from "throttle-debounce";
2
3
 
3
4
  const RESTORE_SCROLL_EVENT_NAME = "restore-scroll";
4
5
  const LOAD_TIME_SCROLL_RESTORE_DELAY = 750;
@@ -8,7 +9,13 @@ declare module globalThis {
8
9
  let singletonScrollManagerConnected: boolean;
9
10
  }
10
11
  // mostly components shouldn't be using this, but there are a few exceptions such as amfReference
12
+
13
+ let scrollUnlocked: boolean = false;
14
+
11
15
  export const restoreScroll = () => {
16
+ if (scrollUnlocked) {
17
+ return;
18
+ }
12
19
  document.body.scrollTop = document.documentElement.scrollTop =
13
20
  window.history.state?.scroll.value;
14
21
  };
@@ -32,13 +39,13 @@ export default class ScrollManager extends LightningElement {
32
39
  */
33
40
 
34
41
  protected scrollCount = 0; // this is for dark magic, basically we lock the user out of scrolling in the first quarter second, unless they really mean it. We do this because load timings mean that the scroll can get messed up in that period
35
- protected scrollUnlocked = false;
36
42
 
37
43
  renderedCallback() {
38
- if (!globalThis.singletonScrollManagerRendered) {
44
+ scrollUnlocked = window.location.hash !== ""; // if we have anchor links, skip the entire scroll restore
45
+ if (!globalThis.singletonScrollManagerRendered && !scrollUnlocked) {
39
46
  globalThis.singletonScrollManagerRendered = true;
40
47
  if (
41
- window.history.state?.scroll.docSize ===
48
+ window.history.state?.scroll?.docSize ===
42
49
  document.body.scrollHeight
43
50
  ) {
44
51
  // only do this if loading is complete and the scrollHeight matches expectations
@@ -49,10 +56,8 @@ export default class ScrollManager extends LightningElement {
49
56
  // sometimes loading is slow, so we may want to reset the scroll to
50
57
  // the correct position after loading is complete, to avoid weird behavior
51
58
  // but only if the user hasn't scrolled around in the meantime
52
- if (!this.scrollUnlocked) {
53
- restoreScroll();
54
- this.scrollUnlocked = true;
55
- }
59
+ restoreScroll();
60
+ scrollUnlocked = true;
56
61
  }, LOAD_TIME_SCROLL_RESTORE_DELAY);
57
62
  }
58
63
  } else {
@@ -75,7 +80,7 @@ export default class ScrollManager extends LightningElement {
75
80
  }
76
81
  }
77
82
 
78
- saveScroll = () => {
83
+ saveScroll = throttle(100, () => {
79
84
  window.history.replaceState(
80
85
  {
81
86
  scroll: {
@@ -86,7 +91,7 @@ export default class ScrollManager extends LightningElement {
86
91
  "",
87
92
  window.location.href
88
93
  );
89
- };
94
+ });
90
95
 
91
96
  manualScrollListener = () => {
92
97
  if (this.scrollCount < 5) {
@@ -1,6 +1,8 @@
1
1
  /*
2
2
  Code generated by scripts/postinstall.js DO NOT EDIT
3
- *//*
3
+ */
4
+
5
+ /*
4
6
  * @param direction vertical or horizontal
5
7
  * @param position type of positioning to apply (relative/absolute)
6
8
  */
@@ -1376,6 +1378,15 @@ Code generated by scripts/postinstall.js DO NOT EDIT
1376
1378
  .coveo-filetype-salesforce-standard-work-order-item,
1377
1379
  .coveo-filetype-salesforce-standard-work-order,
1378
1380
  .coveo-filetype-salesforce-standard-work-type,
1381
+ .coveo-filetype-slack,
1382
+ .coveo-icon.filetype.Slackmessage,
1383
+ .coveo-icon.filetype.slackmessage,
1384
+ .coveo-icon.filetype.Slackchannel,
1385
+ .coveo-icon.filetype.slackchannel,
1386
+ .coveo-icon.filetype.Slackfile,
1387
+ .coveo-icon.filetype.slackfile,
1388
+ .coveo-icon.filetype.Slackuser,
1389
+ .coveo-icon.filetype.slackuser,
1379
1390
  .coveo-filetype-user,
1380
1391
  .coveo-icon.objecttype.People,
1381
1392
  .coveo-icon.objecttype.people,
@@ -1584,21 +1595,6 @@ Code generated by scripts/postinstall.js DO NOT EDIT
1584
1595
  background-size: 56px 64px;
1585
1596
  }
1586
1597
 
1587
- .coveo-filetype-slack,
1588
- .coveo-icon.filetype.Slackmessage,
1589
- .coveo-icon.filetype.slackmessage,
1590
- .coveo-icon.filetype.Slackchannel,
1591
- .coveo-icon.filetype.slackchannel,
1592
- .coveo-icon.filetype.Slackfile,
1593
- .coveo-icon.filetype.slackfile,
1594
- .coveo-icon.filetype.Slackuser,
1595
- .coveo-icon.filetype.slackuser {
1596
- display: inline-block;
1597
- width: 124px;
1598
- height: 124px;
1599
- background-size: 124px 124px;
1600
- }
1601
-
1602
1598
  .coveo-filetype-attachment-small,
1603
1599
  .coveo-icon.objecttype.coveo-small.Attachment,
1604
1600
  .coveo-icon.objecttype.coveo-small.attachment,
@@ -2124,6 +2120,15 @@ Code generated by scripts/postinstall.js DO NOT EDIT
2124
2120
  .coveo-filetype-salesforce-standard-work-order-item-small,
2125
2121
  .coveo-filetype-salesforce-standard-work-order-small,
2126
2122
  .coveo-filetype-salesforce-standard-work-type-small,
2123
+ .coveo-filetype-slack-small,
2124
+ .coveo-icon.filetype.coveo-small.Slackmessage,
2125
+ .coveo-icon.filetype.coveo-small.slackmessage,
2126
+ .coveo-icon.filetype.coveo-small.Slackchannel,
2127
+ .coveo-icon.filetype.coveo-small.slackchannel,
2128
+ .coveo-icon.filetype.coveo-small.Slackfile,
2129
+ .coveo-icon.filetype.coveo-small.slackfile,
2130
+ .coveo-icon.filetype.coveo-small.Slackuser,
2131
+ .coveo-icon.filetype.coveo-small.slackuser,
2127
2132
  .coveo-filetype-user-small,
2128
2133
  .coveo-icon.objecttype.coveo-small.People,
2129
2134
  .coveo-icon.objecttype.coveo-small.people,
@@ -2332,21 +2337,6 @@ Code generated by scripts/postinstall.js DO NOT EDIT
2332
2337
  background-size: 28px 32px;
2333
2338
  }
2334
2339
 
2335
- .coveo-filetype-slack-small,
2336
- .coveo-icon.filetype.coveo-small.Slackmessage,
2337
- .coveo-icon.filetype.coveo-small.slackmessage,
2338
- .coveo-icon.filetype.coveo-small.Slackchannel,
2339
- .coveo-icon.filetype.coveo-small.slackchannel,
2340
- .coveo-icon.filetype.coveo-small.Slackfile,
2341
- .coveo-icon.filetype.coveo-small.slackfile,
2342
- .coveo-icon.filetype.coveo-small.Slackuser,
2343
- .coveo-icon.filetype.coveo-small.slackuser {
2344
- display: inline-block;
2345
- width: 62px;
2346
- height: 62px;
2347
- background-size: 62px 62px;
2348
- }
2349
-
2350
2340
  .coveo-filetype-attachment,
2351
2341
  .coveo-icon.objecttype.Attachment,
2352
2342
  .coveo-icon.objecttype.attachment,
@@ -8065,6 +8055,13 @@ select.coveo-dropdown::-ms-expand {
8065
8055
  * @param $size size of the icon to use
8066
8056
  */
8067
8057
 
8058
+ @font-face {
8059
+ font-family: 'Lato';
8060
+ src: url('https://static.cloud.coveo.com/searchui/v2.10104/0/fonts/lato.woff2'), url('https://staticdev.cloud.coveo.com/searchui/v2.10104/0/fonts/lato.woff2'), url('../fonts/lato.woff2'), url('https://static.cloud.coveo.com/searchui/v2.10104/0/fonts/lato.woff'), url('https://staticdev.cloud.coveo.com/searchui/v2.10104/0/fonts/lato.woff'), url('../fonts/lato.woff');
8061
+ font-weight: normal;
8062
+ font-style: normal;
8063
+ }
8064
+
8068
8065
  .CoveoSearchInterface {
8069
8066
  min-height: 100%;
8070
8067
  position: relative;
@@ -18738,6 +18735,10 @@ button.coveo-user-feedback-banner-explain-why-active {
18738
18735
  * @param $size size of the icon to use
18739
18736
  */
18740
18737
 
18738
+ .coveo-user-explanation-modal .coveo-small-close {
18739
+ display: none;
18740
+ }
18741
+
18741
18742
  .coveo-user-explanation-modal-explanation-section {
18742
18743
  display: flex;
18743
18744
  flex-wrap: wrap;
@@ -1,5 +1,6 @@
1
1
  import { LightningElement, api, track } from "lwc";
2
2
  import type * as CoveoSDK from "coveo-search-ui";
3
+ import { track as trackGTM } from "dxUtils/analytics";
3
4
 
4
5
  interface CoveoSearch {
5
6
  state: typeof CoveoSDK.state;
@@ -151,6 +152,7 @@ export default class SearchResults extends LightningElement {
151
152
  return cv.state === "selected";
152
153
  });
153
154
  });
155
+ this.trackSearchResults(event, this.query, this.totalResults);
154
156
  });
155
157
  }
156
158
 
@@ -184,4 +186,13 @@ export default class SearchResults extends LightningElement {
184
186
  }
185
187
  }
186
188
  }
189
+
190
+ private trackSearchResults(event: Event, term: string, resultCount: any) {
191
+ trackGTM(event.target!, "custEv_search", {
192
+ search_term: term,
193
+ search_category: "",
194
+ search_type: "site search",
195
+ search_result_count: resultCount
196
+ });
197
+ }
187
198
  }
@@ -32,9 +32,20 @@ export default class Tab extends LightningElement {
32
32
 
33
33
  private sendGtm(e: PointerEvent) {
34
34
  track(e.currentTarget!, "custEv_topNavLinkClick", {
35
- navHeading: this.label || undefined,
36
- clickText: this.href || undefined,
37
- pageLocation: window.location.pathname
35
+ click_text: this.label || undefined,
36
+ click_url: this.href || undefined,
37
+ nav_level: "1",
38
+ nav_item: this.label || undefined,
39
+ element_type: "link",
40
+ nav_type: "global"
41
+ });
42
+
43
+ track(e.currentTarget!, "custEv_linkClick", {
44
+ click_text: this.label,
45
+ element_title: "button",
46
+ click_url: this.href,
47
+ element_type: "link",
48
+ content_category: "cta"
38
49
  });
39
50
  }
40
51
 
@@ -8,7 +8,7 @@
8
8
  aria-controls={panelId}
9
9
  aria-label="Tab button"
10
10
  aria-selected={active}
11
- tabindex={index}
11
+ tabindex="0"
12
12
  >
13
13
  <slot></slot>
14
14
  </button>
@@ -41,6 +41,11 @@ const enum PlatformEventsType {
41
41
  UserMerge = "UserMerge"
42
42
  }
43
43
 
44
+ // TODO: We probably should rewrite the logic of this component once the new identity stuff is released,
45
+ // especially because SSO should work in all browsers at that point, and we won't need to do any weird
46
+ // checking for the presence of SFIDWidget (and all the associated pain from trying to "time" the triggering of login
47
+ // events due to logins coming via two distinct routes in Chrome).
48
+
44
49
  // This component handles the core Salesforce OAuth 2.0 flow used by TBID for login, rendering a
45
50
  // simple Login button or avatar icon depending on login status. It should be reusable in any case
46
51
  // where TBID login is needed.
@@ -56,6 +61,7 @@ export default class TbidAvatarButton extends LightningElement {
56
61
  private eventSource?: EventSource;
57
62
  private _isRequestingUserInfo = false;
58
63
  private _didReceiveUserInfo = false;
64
+ private _didDispatchLoginSuccess = false;
59
65
 
60
66
  // _didReceiveUserInfo is a proxy for "isLoggedIn" because we only want to show the UI corresponding
61
67
  // to logged-in state when user info has been received (avoids flashes of weird content)
@@ -155,8 +161,12 @@ export default class TbidAvatarButton extends LightningElement {
155
161
  // then this method waits a maximum of 1 second for the widget to finish logging in. If it does
156
162
  // *not* respond quickly, the method resolves early so that we can stop waiting pointlessly.
157
163
  private waitForSFIDWidget = async () => {
164
+ const millisecondsFor30fps = 33;
158
165
  const widgetNotAlivePromise = new Promise<boolean>((resolve) => {
159
- const timeoutId = setTimeout(() => resolve(false), 100);
166
+ const timeoutId = setTimeout(
167
+ () => resolve(false),
168
+ 5 * millisecondsFor30fps // give SFIDWidget roughly 5 chances to load
169
+ );
160
170
  window.SFIDWidget?.isAlive({
161
171
  // If the widget responds as expected, this promise will fail to resolve and thus
162
172
  // we will continue to wait on the other promise, below.
@@ -165,7 +175,7 @@ export default class TbidAvatarButton extends LightningElement {
165
175
  });
166
176
  const widgetLoggedInPromise = pollUntil(
167
177
  () => Boolean(window.SFIDWidget?.openid_response),
168
- 100,
178
+ millisecondsFor30fps,
169
179
  1000
170
180
  );
171
181
  const success = await Promise.race([
@@ -279,12 +289,13 @@ export default class TbidAvatarButton extends LightningElement {
279
289
  // on SSO login. For some reason, the user info on SSO login lacks the user's photo URLs, so we
280
290
  // have to re-request it. Maybe something to bring up with the TBID team.
281
291
  await this.requestUserInfo();
282
- this.dispatchLoginSuccess();
292
+ this.dispatchLoginSuccess(true);
283
293
  } else if (
284
294
  tokenResponse.status === 409 &&
285
- !this._didReceiveUserInfo
295
+ !this._didReceiveUserInfo &&
296
+ !this._isRequestingUserInfo // don't duplicate requests when we already have this token and a request is on-going
286
297
  ) {
287
- // Valid token received, but we still need to grab userInfo. This could happen in certain edge cases
298
+ // Valid but duplicate token received, but we still need to grab userInfo. This could happen in certain edge cases
288
299
  // involving "seamless SSO" (e.g., you might be sitting on our page in more than one tab, then log in
289
300
  // on one of them; in that case, our back-end will receive the token from one tab, end up _rejecting_ it
290
301
  // as a duplicate on other tabs where the login event occurs, and yet still need to request user info
@@ -384,11 +395,6 @@ export default class TbidAvatarButton extends LightningElement {
384
395
  };
385
396
 
386
397
  private requestUserInfo = async () => {
387
- if (this._isRequestingUserInfo) {
388
- // prevent duplicate requests
389
- return;
390
- }
391
-
392
398
  this._isRequestingUserInfo = true;
393
399
 
394
400
  try {
@@ -422,12 +428,16 @@ export default class TbidAvatarButton extends LightningElement {
422
428
  );
423
429
  };
424
430
 
425
- private dispatchLoginSuccess = () => {
426
- this.dispatchEvent(
427
- new CustomEvent("tbidloginsuccess", {
428
- bubbles: true
429
- })
430
- );
431
+ private dispatchLoginSuccess = (receivedNewToken = false) => {
432
+ // Receiving a new token should be treated as a new (SSO) login.
433
+ if (!this._didDispatchLoginSuccess || receivedNewToken) {
434
+ this._didDispatchLoginSuccess = true;
435
+ this.dispatchEvent(
436
+ new CustomEvent("tbidloginsuccess", {
437
+ bubbles: true
438
+ })
439
+ );
440
+ }
431
441
  };
432
442
 
433
443
  private dispatchLogoutSuccess = () => {
@@ -75,16 +75,33 @@ export default class Toc extends LightningElement {
75
75
 
76
76
  this._value = id!;
77
77
 
78
- sendGtm(e.currentTarget!, "custEv_tableOfContentsClick", {
79
- clickText: text,
80
- clickUrl: href,
81
- elementType: "link",
82
- locationOnPage: "table of contents",
83
- itemTitle: this.title
78
+ sendGtm(e.currentTarget!, "custEv_tableOfContents", {
79
+ click_text: text,
80
+ clickAction: "click",
81
+ click_url: href,
82
+ element_title: "a",
83
+ element_type: "link",
84
+ content_category: "cta"
84
85
  });
85
86
  }
86
87
 
87
- private toggleShowContent() {
88
+ private toggleShowContent(e: Event) {
88
89
  this.showContent = !this.showContent;
90
+ this.trackShowContent(e);
91
+ }
92
+
93
+ private trackShowContent(event: Event) {
94
+ let action = "collapse";
95
+ if (this.showContent) {
96
+ action = "expand";
97
+ }
98
+ sendGtm(event.target!, "custEv_tableOfContents", {
99
+ click_text: this.toggleButtonAriaLabel,
100
+ clickAction: action,
101
+ click_url: "",
102
+ element_title: "dx-button",
103
+ element_type: "button",
104
+ content_category: "cta"
105
+ });
89
106
  }
90
107
  }