@salesforcedevs/dx-components 1.28.4 → 1.28.5-node22-1

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 (35) hide show
  1. package/lwc.config.json +6 -1
  2. package/package.json +46 -47
  3. package/src/modules/dx/codeBlock/codeBlock.ts +6 -6
  4. package/src/modules/dx/emptyState/emptyState.html +1 -1
  5. package/src/modules/dx/emptyState/emptyState.ts +2 -2
  6. package/src/modules/dx/error/error.css +3 -3
  7. package/src/modules/dx/errorFallback/errorFallback.html +1 -1
  8. package/src/modules/dx/feature/feature.css +2 -2
  9. package/src/modules/dx/feature/feature.html +1 -1
  10. package/src/modules/dx/featuredContentHeader/featuredContentHeader.css +5 -5
  11. package/src/modules/dx/featuredContentHeader/svgs.ts +2 -2
  12. package/src/modules/dx/footer/footer.css +176 -4
  13. package/src/modules/dx/footer/footer.html +90 -3
  14. package/src/modules/dx/footer/footer.ts +276 -1
  15. package/src/modules/dx/footerMfe/footerMfe.ts +1 -0
  16. package/src/modules/dx/globalHeader/globalHeader.html +5 -0
  17. package/src/modules/dx/globalHeader/globalHeader.ts +84 -0
  18. package/src/modules/dx/input/input.ts +1 -1
  19. package/src/modules/dx/logo/logo.ts +1 -1
  20. package/src/modules/dx/searchResults/resultsTemplate.js +1 -1
  21. package/src/modules/dx/searchResults/searchResults.css +4 -4
  22. package/src/modules/dx/sectionBanner/sectionBanner.html +1 -1
  23. package/src/modules/dx/sidebar/sidebar.html +1 -1
  24. package/src/modules/dx/socials/socials.css +36 -0
  25. package/src/modules/dx/socials/socials.html +22 -0
  26. package/src/modules/dx/socials/socials.ts +76 -0
  27. package/src/modules/dx/treeItem/treeItem.html +1 -1
  28. package/src/modules/dxUtils/shiki/__mocks__/shiki.ts +2 -1
  29. package/src/modules/dxUtils/shiki/__mocks__/shikijs.ts +6 -0
  30. package/src/modules/dxUtils/shiki/shiki.ts +26 -176
  31. package/src/modules/dxUtils/shikiCore/shikiCore.ts +188 -0
  32. package/src/modules/dxUtils/shikiGrammars/shikiGrammars.ts +151 -41
  33. package/src/modules/dxUtils/shikiStatic/shikiStatic.html +3 -0
  34. package/src/modules/dxUtils/shikiStatic/shikiStatic.ts +46 -0
  35. package/LICENSE +0 -12
@@ -4,7 +4,7 @@ import { FooterVariant, LightningSlotElement } from "typings/custom";
4
4
  import { track } from "dxUtils/analytics";
5
5
  import { isSlotEmpty } from "dxUtils/slot";
6
6
 
7
- export default class Footer extends LightningElement {
7
+ class Footer extends LightningElement {
8
8
  private _variant: FooterVariant = "small-signup";
9
9
  private isSlotEmpty = true;
10
10
  private signupUrl =
@@ -16,6 +16,9 @@ export default class Footer extends LightningElement {
16
16
  @api
17
17
  mfeConfigOrigin: string = `${window.location.origin}/developer/en-us/wp-json`;
18
18
 
19
+ @api
20
+ useMfe: boolean = false; // TODO: Probably just remove this once the footer MFE supports rendering in non-full-width contexts
21
+
19
22
  @api
20
23
  set variant(value: FooterVariant) {
21
24
  if (!this.isFooterVariant(value)) {
@@ -96,3 +99,275 @@ export default class Footer extends LightningElement {
96
99
  });
97
100
  }
98
101
  }
102
+
103
+ // This encapsulates the old non-MFE footer functionality that should be removed once the footer MFE supports rendering in non-full-width contexts.
104
+ // It's encapsulated like this solely for ease of deletion later.
105
+ function augmentWithNonMFEFooterFunctionality(FooterClass: typeof Footer) {
106
+ const baseSocialIconUrl = `/assets/icons/brand-sprite/svg/symbols.svg`;
107
+
108
+ return class NonMFEFooter extends FooterClass {
109
+ private didRequestFooterConfig = false;
110
+ private footerConfig: any = []; // NOTE: See mockProps.ts for the expected format of the footer config
111
+ private configItemTitleToItemLookup: Map<string, any> = new Map();
112
+ private configItemParentToChildrenLookup: Map<number, any[]> =
113
+ new Map();
114
+ private generalLinks: any = [];
115
+ private socialLinks: any = [];
116
+ private termsLinks: any = [];
117
+
118
+ async renderedCallback() {
119
+ super.renderedCallback?.();
120
+
121
+ if (this.didRequestFooterConfig || this.useMfe) {
122
+ return;
123
+ }
124
+
125
+ this.didRequestFooterConfig = true;
126
+ this.footerConfig = await this.requestFooterConfig();
127
+ this.buildFooterConfigLookupTables(this.footerConfig);
128
+ this.setSocialLinks();
129
+ this.setGeneralLinks();
130
+ this.setLegalFooter();
131
+ }
132
+
133
+ get showContainerMiddle() {
134
+ return !this.showLegalOnly;
135
+ }
136
+
137
+ private async requestFooterConfig() {
138
+ let config: any = [];
139
+ try {
140
+ const response = await fetch(
141
+ `${this.mfeConfigOrigin}/c360/experience/v1/navigation`
142
+ );
143
+ const data = await response.json();
144
+ config = data["footer-navigation"] || [];
145
+ } catch (error) {
146
+ console.error("Error requesting footer config:", error);
147
+ }
148
+ return config;
149
+ }
150
+
151
+ private buildFooterConfigLookupTables(config: any[]) {
152
+ config.forEach((item: any) => {
153
+ // attr_title is preferable to title because it is not language-specific
154
+ this.configItemTitleToItemLookup.set(item.attr_title || item.title, item);
155
+ if (item.menu_item_parent) {
156
+ const parentId = parseInt(item.menu_item_parent, 10);
157
+ const children =
158
+ this.configItemParentToChildrenLookup.get(parentId) ||
159
+ [];
160
+ children.push(item);
161
+ this.configItemParentToChildrenLookup.set(
162
+ parentId,
163
+ children
164
+ );
165
+ }
166
+ });
167
+ }
168
+
169
+ private setSocialLinks() {
170
+ const socialLinksParentItem =
171
+ this.configItemTitleToItemLookup.get("social-icons");
172
+
173
+ if (!socialLinksParentItem || !socialLinksParentItem.ID) {
174
+ console.error(
175
+ "Social links parent item not found in footer config"
176
+ );
177
+ return;
178
+ }
179
+
180
+ const socialLinksItems = this.configItemParentToChildrenLookup.get(
181
+ socialLinksParentItem.ID
182
+ );
183
+
184
+ if (!socialLinksItems) {
185
+ console.error(
186
+ "Social links children items not found in footer config"
187
+ );
188
+ return;
189
+ }
190
+
191
+ this.socialLinks =
192
+ socialLinksItems.map((child: any) => {
193
+ const childTitle: string = child.attr_title || child.title || ""; // attr_title is preferable to title because it is not language-specific
194
+ const iconSymbol =
195
+ childTitle === "LinkedIn"
196
+ ? "linked-in"
197
+ : childTitle.toLocaleLowerCase();
198
+ const iconUrlHash =
199
+ iconSymbol === "twitter"
200
+ ? "#twitter-x"
201
+ : `#themed-${iconSymbol}`;
202
+
203
+ return {
204
+ href: child.url,
205
+ iconSprite: "brand",
206
+ iconURL: `${baseSocialIconUrl}${iconUrlHash}`,
207
+ label: child.title,
208
+ iconSymbol
209
+ };
210
+ });
211
+ }
212
+
213
+ private setGeneralLinks() {
214
+ const topFooterItem =
215
+ this.configItemTitleToItemLookup.get("top-footer");
216
+ const mediaItem = this.configItemTitleToItemLookup.get("media");
217
+
218
+ if (!topFooterItem || !topFooterItem.ID) {
219
+ console.error("Top footer item not found in footer config");
220
+ return;
221
+ }
222
+
223
+ const generalLinksHeadingsItems =
224
+ this.configItemParentToChildrenLookup
225
+ .get(topFooterItem.ID)
226
+ ?.filter((item: any) => item.ID !== mediaItem?.ID);
227
+
228
+ if (!generalLinksHeadingsItems) {
229
+ console.error(
230
+ "General links children items not found in footer config"
231
+ );
232
+ return;
233
+ }
234
+
235
+ const generalLinks: any[] = [];
236
+ generalLinksHeadingsItems.forEach((item: any) => {
237
+ const childrenItems = this.configItemParentToChildrenLookup.get(item.ID);
238
+
239
+ if (!childrenItems) {
240
+ console.error(`General links children items not found for item with title "${item.title}"`);
241
+ return;
242
+ }
243
+
244
+ generalLinks.push({
245
+ id: item.title,
246
+ label: item.title,
247
+ options: childrenItems.map((option: any) => {
248
+ return {
249
+ id: option.title,
250
+ label: option.title,
251
+ link: {
252
+ href: option.url,
253
+ target: option.target
254
+ }
255
+ };
256
+ })
257
+ });
258
+ });
259
+ this.generalLinks = generalLinks;
260
+ }
261
+
262
+ private setLegalFooter() {
263
+ const copyrightNoticeItem = this.configItemTitleToItemLookup.get("All rights reserved");
264
+
265
+ if (!copyrightNoticeItem) {
266
+ console.error("All rights reserved item not found in footer config");
267
+ return;
268
+ }
269
+
270
+ const { url, description } = copyrightNoticeItem;
271
+ const copyrightNoticeInnerHtml = description.replace("{{All rights reserved}}", `<a href="${url}">All rights reserved</a>`);
272
+ const copyrightNoticeEl = this.template.querySelector(".copyright-notice")!;
273
+ copyrightNoticeEl.innerHTML = copyrightNoticeInnerHtml;
274
+ const copyrightLink = copyrightNoticeEl.querySelector("a");
275
+ if (copyrightLink) {
276
+ copyrightLink.addEventListener("click", (e) =>
277
+ this.handleCopyrightLinkClick(e as Event)
278
+ );
279
+ }
280
+
281
+ const legalLinksItems = this.configItemParentToChildrenLookup.get(copyrightNoticeItem.ID);
282
+
283
+ if (!legalLinksItems) {
284
+ console.error("Legal links children items not found in footer config");
285
+ return;
286
+ }
287
+
288
+ this.termsLinks = legalLinksItems.map((item: any) => {
289
+ const link: any = {
290
+ label: item.title,
291
+ href: item.url,
292
+ target: item.target,
293
+ rel: item.rel
294
+ };
295
+
296
+ const itemTitle: string = item.attr_title || item.title || ""; // attr_title is preferable to title because it is not language-specific
297
+ if (itemTitle === "Cookie Preferences") {
298
+ link.href = "#";
299
+ link.onclick = this.handleCookiePreferencesClick;
300
+ } else if (itemTitle === "Your Privacy Choices") {
301
+ link.img = "https://developer.salesforce.com/ns-assets/privacyoptions.svg";
302
+ }
303
+
304
+ return link;
305
+ });
306
+ }
307
+
308
+ private openOneTrustInfoDisplay() {
309
+ if ((window as any).OneTrust && (window as any).OneTrust.ToggleInfoDisplay) {
310
+ (window as any).OneTrust.ToggleInfoDisplay();
311
+ }
312
+ }
313
+
314
+ private trackFooterLinkClick(
315
+ e: Event,
316
+ options?: {
317
+ label?: string;
318
+ elementTitle?: string;
319
+ clickUrl?: string;
320
+ }
321
+ ) {
322
+ const anchor = e.currentTarget as HTMLAnchorElement;
323
+ const clickText =
324
+ options?.label ??
325
+ anchor.textContent?.trim() ??
326
+ anchor.getAttribute("aria-label") ??
327
+ anchor.href;
328
+ const elementTitle = options?.elementTitle ?? clickText;
329
+ const clickUrl = options?.clickUrl ?? anchor.href;
330
+
331
+ track(anchor, "custEv_linkClick", {
332
+ click_text: clickText,
333
+ click_url: clickUrl,
334
+ element_type: "link",
335
+ element_title: elementTitle,
336
+ content_category: "footer",
337
+ nav_item: clickText,
338
+ nav_level: "1",
339
+ nav_type: "footer"
340
+ });
341
+ }
342
+
343
+ private handleLogoClick(e: Event) {
344
+ this.trackFooterLinkClick(e, {
345
+ label: "Salesforce",
346
+ elementTitle: "Salesforce logo",
347
+ clickUrl: `${window.location.origin}/`
348
+ });
349
+ }
350
+
351
+ private handleSocialLinkClick(e: Event) {
352
+ this.trackFooterLinkClick(e);
353
+ }
354
+
355
+ private handleCookiePreferencesClick(e: Event) {
356
+ this.trackFooterLinkClick(e, {
357
+ label: "Cookie Preferences",
358
+ clickUrl: (e.currentTarget as HTMLAnchorElement).href || "#"
359
+ });
360
+ this.openOneTrustInfoDisplay();
361
+ }
362
+
363
+ private handleTermsClick(e: Event) {
364
+ this.trackFooterLinkClick(e);
365
+ }
366
+
367
+ private handleCopyrightLinkClick(e: Event) {
368
+ this.trackFooterLinkClick(e, { label: "All rights reserved" });
369
+ }
370
+ };
371
+ }
372
+
373
+ export default augmentWithNonMFEFooterFunctionality(Footer);
@@ -15,6 +15,7 @@ export default class FooterMfe extends LightningElement {
15
15
  this.footerElement = document.createElement("hgf-footer");
16
16
  this.footerElement.setAttribute("origin", this.origin);
17
17
  this.footerElement.setAttribute("hide-language-selector", "true");
18
+ this.footerElement.setAttribute("home-href", this.homeHref);
18
19
  if (this.legalOnly) {
19
20
  this.footerElement.setAttribute("legal-only", "true");
20
21
  }
@@ -0,0 +1,5 @@
1
+ <template lwc:render-mode="light">
2
+ <div lwc:ref="globalNavContainer" part="container">
3
+ <dx-skip-nav-link></dx-skip-nav-link>
4
+ </div>
5
+ </template>
@@ -0,0 +1,84 @@
1
+ import kebabCase from "lodash.kebabcase";
2
+ import { LightningElement, api } from "lwc";
3
+
4
+ const defaultDomain = "https://developer.salesforce.com";
5
+ const defaultLocale = "en-us";
6
+ const defaultHeaderSettingsBasePath = "/c/public/header-settings";
7
+ const headerSettingsJsonKeys = [
8
+ "regionSelectorOverride",
9
+ "contactLinksOverride"
10
+ ];
11
+
12
+ export default class DevExNavigation extends LightningElement {
13
+ static renderMode = "light";
14
+
15
+ @api locale: string = defaultLocale;
16
+ @api path: string = defaultHeaderSettingsBasePath;
17
+ @api domain: string = defaultDomain;
18
+
19
+ async connectedCallback(): Promise<void> {
20
+ try {
21
+ const headerSettingsResponse = await fetch(
22
+ `${this.domain}${this.path}/${this.locale}.json`,
23
+ {
24
+ headers: {
25
+ "Content-Type": "application/json"
26
+ }
27
+ }
28
+ );
29
+ if (headerSettingsResponse.ok) {
30
+ this.createFullNav(await headerSettingsResponse.json());
31
+ } else {
32
+ this.createBarebonesNav();
33
+ }
34
+ } catch (ex) {
35
+ console.error(`Navigation error: ${ex}`);
36
+ this.createBarebonesNav();
37
+ }
38
+ }
39
+
40
+ private createGlobalNav(globalNavSettings: any): HTMLElement {
41
+ const hgfNav = document.createElement("hgf-c360nav");
42
+
43
+ Object.entries(globalNavSettings).forEach(([key, value]) => {
44
+ if (headerSettingsJsonKeys.includes(key)) {
45
+ value = JSON.stringify(value);
46
+ }
47
+ hgfNav.setAttribute(kebabCase(key), value as string);
48
+ });
49
+
50
+ return hgfNav;
51
+ }
52
+
53
+ private createContextNav(contextNavData: any): HTMLElement {
54
+ const hgfNavContext = document.createElement("hgf-c360contextnav");
55
+ hgfNavContext.setAttribute("data", JSON.stringify(contextNavData));
56
+ return hgfNavContext;
57
+ }
58
+
59
+ private createFullNav(headerData: any): void {
60
+ const hgfNav = this.createGlobalNav(headerData.headerSettings);
61
+ const hgfNavContext = this.createContextNav(headerData.navItems);
62
+ const containerEl = this.refs.globalNavContainer as Element;
63
+ containerEl.appendChild(hgfNav);
64
+ containerEl.appendChild(hgfNavContext);
65
+ }
66
+
67
+ private createBarebonesNav(): void {
68
+ const hgfNav = this.createGlobalNav({
69
+ origin: "",
70
+ contextNavEnabled: "true"
71
+ });
72
+ const hgfNavContext = this.createContextNav({
73
+ variation: "static",
74
+ propertyTitle: {
75
+ label: "Developers",
76
+ url: "/"
77
+ },
78
+ menuGroup: {}
79
+ });
80
+ const containerEl = this.refs.globalNavContainer as Element;
81
+ containerEl.appendChild(hgfNav);
82
+ containerEl.appendChild(hgfNavContext);
83
+ }
84
+ }
@@ -195,7 +195,7 @@ export default class Input extends LightningElement {
195
195
  }
196
196
 
197
197
  private get shortcutImgSrc() {
198
- return `https://a.sfdcstatic.com/developer-website/prod/images/${this.commandKey}.svg`;
198
+ return `https://developer.salesforce.com/ns-assets/${this.commandKey}.svg`;
199
199
  }
200
200
 
201
201
  private get shortcutImgAlt() {
@@ -3,7 +3,7 @@ import { LightningElement, api } from "lwc";
3
3
  export default class Logo extends LightningElement {
4
4
  @api href: string = "/";
5
5
  @api imgSrc: string =
6
- "https://a.sfdcstatic.com/developer-website/prod/images/salesforce-cloud.svg";
6
+ "https://developer.salesforce.com/ns-assets/salesforce-cloud.svg";
7
7
  @api imgAlt: string = "Salesforce logo";
8
8
  @api label!: string;
9
9
  }
@@ -9,7 +9,7 @@ export default html`
9
9
  <div class="coveo-show-if-no-results">
10
10
  <div class="no-results">
11
11
  <img
12
- src="https://a.sfdcstatic.com/developer-website/prod/images/binary-cloud-circle-small.svg"
12
+ src="https://developer.salesforce.com/ns-assets/binary-cloud-circle-small.svg"
13
13
  alt="purple cloud with floating binary numbers above"
14
14
  />
15
15
  <div class="no-results-info">
@@ -311,7 +311,7 @@ li.coveo-dynamic-facet-breadcrumb-value-list-item {
311
311
 
312
312
  .dx-search-header {
313
313
  padding: var(--dx-g-spacing-xl);
314
- background: url("https://a.sfdcstatic.com/developer-website/prod/images/binary-cloud-trees.svg")
314
+ background: url("https://developer.salesforce.com/ns-assets/binary-cloud-trees.svg")
315
315
  no-repeat 100% 0,
316
316
  linear-gradient(
317
317
  77deg,
@@ -352,7 +352,7 @@ li.coveo-dynamic-facet-breadcrumb-value-list-item {
352
352
 
353
353
  @media (min-width: 928px) and (max-width: 1024px) {
354
354
  .dx-search-header {
355
- background: url("https://a.sfdcstatic.com/developer-website/prod/images/binary-cloud-trees.svg")
355
+ background: url("https://developer.salesforce.com/ns-assets/binary-cloud-trees.svg")
356
356
  no-repeat 200% 0,
357
357
  linear-gradient(
358
358
  77deg,
@@ -380,7 +380,7 @@ li.coveo-dynamic-facet-breadcrumb-value-list-item {
380
380
 
381
381
  @media (min-width: 768px) and (max-width: 928px) {
382
382
  .dx-search-header {
383
- background: url("https://a.sfdcstatic.com/developer-website/prod/images/binary-trees.svg")
383
+ background: url("https://developer.salesforce.com/ns-assets/binary-trees.svg")
384
384
  no-repeat 90% 50%,
385
385
  linear-gradient(
386
386
  77deg,
@@ -392,7 +392,7 @@ li.coveo-dynamic-facet-breadcrumb-value-list-item {
392
392
 
393
393
  @media (max-width: 768px) {
394
394
  .dx-search-header {
395
- background: url("https://a.sfdcstatic.com/developer-website/prod/images/binary.svg")
395
+ background: url("https://developer.salesforce.com/ns-assets/binary.svg")
396
396
  no-repeat 90% 50%,
397
397
  linear-gradient(
398
398
  77deg,
@@ -3,7 +3,7 @@
3
3
  <img
4
4
  if:false={hideTopGraphic}
5
5
  class="graphic"
6
- src="https://a.sfdcstatic.com/developer-website/prod/images/dx-section-banner-graphic-1.svg"
6
+ src="https://developer.salesforce.com/ns-assets/dx-section-banner-graphic-1.svg"
7
7
  alt=""
8
8
  />
9
9
  <div class="content">
@@ -69,7 +69,7 @@
69
69
  <img
70
70
  lwc:if={isSearchLoading}
71
71
  class="loading-skeleton padding-horizontal"
72
- src="https://a.sfdcstatic.com/developer-website/prod/images/sidebar-loading.svg"
72
+ src="https://developer.salesforce.com/ns-assets/sidebar-loading.svg"
73
73
  alt="loading"
74
74
  />
75
75
  <template
@@ -0,0 +1,36 @@
1
+ :host {
2
+ display: inline-block;
3
+ }
4
+
5
+ .details-section {
6
+ color: var(--dx-g-text-label-color);
7
+ font-family: var(--dx-g-font-display);
8
+ font-size: var(--dx-g-text-xs);
9
+ letter-spacing: 0.7px;
10
+ margin-bottom: var(--dx-g-spacing-sm);
11
+ text-transform: uppercase;
12
+ }
13
+
14
+ .details-section span {
15
+ display: block;
16
+ line-height: var(--dx-g-spacing-md);
17
+ }
18
+
19
+ .socials-group {
20
+ display: flex;
21
+ }
22
+
23
+ .socials-group a:not(:last-child) {
24
+ margin-right: var(--dx-g-spacing-md);
25
+ }
26
+
27
+ dx-icon-badge {
28
+ --dx-c-icon-badge-background-color: var(--dx-g-social-svg-background-color);
29
+ --dx-c-icon-badge-color: var(--dx-g-social-svg-icon-color);
30
+ --dx-c-icon-badge-size: 34px;
31
+ --dx-c-icon-badge-icon-size: 18px;
32
+ }
33
+
34
+ dx-icon-badge:hover {
35
+ --dx-c-icon-badge-background-color: var(--dx-g-social-svg-hover-color);
36
+ }
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div class="details-section" if:true={hasDetails}>
3
+ <template for:each={details} for:item="detail">
4
+ <span key={detail}>{detail}</span>
5
+ </template>
6
+ </div>
7
+ <div class="socials-group">
8
+ <template for:each={socials} for:item="social">
9
+ <a
10
+ href={social.href}
11
+ key={social.name}
12
+ target={linksTarget}
13
+ title={social.name}
14
+ >
15
+ <dx-icon-badge
16
+ sprite={spriteName}
17
+ symbol={social.symbol}
18
+ ></dx-icon-badge>
19
+ </a>
20
+ </template>
21
+ </div>
22
+ </template>
@@ -0,0 +1,76 @@
1
+ import { LightningElement, api } from "lwc";
2
+ import { normalizeToArray } from "dxUtils/normalizers";
3
+
4
+ type Icon = { name: string; symbol: string; baseShareUrl: string };
5
+
6
+ const URL_TOKEN = "{url}";
7
+ const TEXT_TOKEN = "{text}";
8
+
9
+ const SOCIAL_ICONS: Array<Icon> = [
10
+ {
11
+ name: "Twitter",
12
+ symbol: "twitter",
13
+ baseShareUrl: `https://twitter.com/intent/tweet?url=${URL_TOKEN}&text=${TEXT_TOKEN}`
14
+ },
15
+ {
16
+ name: "Linkedin",
17
+ symbol: "linkedin-in",
18
+ baseShareUrl: `http://www.linkedin.com/sharing/share-offsite/?url=${URL_TOKEN}`
19
+ }
20
+ ];
21
+
22
+ const SPRITE_NAME = "brand";
23
+
24
+ const VALID_TARGETS = ["_self", "_blank", "_parent", "_top", "framename"];
25
+
26
+ export default class Socials extends LightningElement {
27
+ private _linksTarget: string = "_blank";
28
+ private _details: Array<string> = [];
29
+
30
+ @api
31
+ get details() {
32
+ return this._details;
33
+ }
34
+
35
+ set details(value) {
36
+ this._details = normalizeToArray(value, "|") as Array<string>;
37
+ }
38
+
39
+ @api
40
+ get linksTarget(): string {
41
+ return this._linksTarget;
42
+ }
43
+
44
+ set linksTarget(value: string) {
45
+ if (value && !VALID_TARGETS.includes(value)) {
46
+ console.error(
47
+ `Invalid links target, valid options are: ${VALID_TARGETS.join(
48
+ ", "
49
+ )}`
50
+ );
51
+ return;
52
+ }
53
+ this._linksTarget = value;
54
+ }
55
+
56
+ get socials() {
57
+ const url = encodeURIComponent(document.location.href);
58
+ const text = encodeURIComponent(document.title);
59
+ return SOCIAL_ICONS.map(({ baseShareUrl, ...value }: Icon) => {
60
+ return {
61
+ ...value,
62
+ href: baseShareUrl
63
+ .replace(URL_TOKEN, url)
64
+ .replace(TEXT_TOKEN, text)
65
+ };
66
+ });
67
+ }
68
+
69
+ get spriteName() {
70
+ return SPRITE_NAME;
71
+ }
72
+
73
+ get hasDetails() {
74
+ return this.details && this.details.length > 0;
75
+ }
76
+ }
@@ -16,7 +16,7 @@
16
16
  <img
17
17
  if:true={showChildrenLoading}
18
18
  class="tree-children-loading"
19
- src="https://a.sfdcstatic.com/developer-website/prod/images/sidebar-item-loading.svg"
19
+ src="https://developer.salesforce.com/ns-assets/sidebar-item-loading.svg"
20
20
  alt="childrenLoading"
21
21
  />
22
22
  <template if:true={showChildren} tabindex="0">
@@ -34,7 +34,8 @@ export const createHighlighter = jest.fn().mockImplementation(() => {
34
34
  "apex",
35
35
  "text",
36
36
  "handlebars"
37
- ])
37
+ ]),
38
+ loadLanguage: jest.fn().mockResolvedValue(undefined)
38
39
  });
39
40
  });
40
41
 
@@ -21,3 +21,9 @@ export default mockLanguage;
21
21
  // Also export as named export for themes
22
22
  export const theme = mockTheme;
23
23
  export const lang = mockLanguage;
24
+
25
+ // Mock for @shikijs/colorized-brackets
26
+ export const transformerColorizedBrackets = () => ({
27
+ name: "colorized-brackets",
28
+ preprocess: (code: string) => code
29
+ });