@salesforcedevs/docs-components 0.6.0 → 0.7.59-sppage-alpha1

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 (102) hide show
  1. package/lwc.config.json +17 -3
  2. package/package.json +16 -6
  3. package/src/modules/README.md +41 -0
  4. package/src/modules/doc/amfModelParser/amfModelParser.ts +674 -0
  5. package/src/modules/doc/amfReference/amfReference.css +25 -0
  6. package/src/modules/doc/amfReference/amfReference.html +60 -0
  7. package/src/modules/doc/amfReference/amfReference.ts +1494 -0
  8. package/src/modules/doc/amfReference/constants.ts +76 -0
  9. package/src/modules/doc/amfReference/types.ts +125 -0
  10. package/src/modules/doc/amfTopic/amfTopic.css +21 -0
  11. package/src/modules/doc/amfTopic/amfTopic.html +3 -0
  12. package/src/modules/doc/amfTopic/amfTopic.ts +111 -0
  13. package/src/modules/doc/amfTopic/types.ts +56 -0
  14. package/src/modules/doc/amfTopic/utils.ts +136 -0
  15. package/src/modules/doc/breadcrumbItem/breadcrumbItem.css +51 -0
  16. package/src/modules/doc/breadcrumbItem/breadcrumbItem.html +5 -0
  17. package/src/modules/doc/breadcrumbItem/breadcrumbItem.ts +71 -0
  18. package/src/modules/doc/breadcrumbs/breadcrumbs.css +27 -0
  19. package/src/modules/doc/breadcrumbs/breadcrumbs.html +58 -0
  20. package/src/modules/doc/breadcrumbs/breadcrumbs.ts +192 -0
  21. package/src/modules/doc/content/content.css +94 -70
  22. package/src/modules/doc/content/content.ts +233 -169
  23. package/src/modules/doc/contentCallout/contentCallout.css +17 -23
  24. package/src/modules/doc/contentCallout/contentCallout.html +13 -4
  25. package/src/modules/doc/contentCallout/contentCallout.ts +16 -3
  26. package/src/modules/doc/contentLayout/contentLayout.css +131 -0
  27. package/src/modules/doc/contentLayout/contentLayout.html +64 -0
  28. package/src/modules/doc/contentLayout/contentLayout.ts +610 -0
  29. package/src/modules/doc/doDont/doDont.css +47 -0
  30. package/src/modules/doc/doDont/doDont.html +27 -0
  31. package/src/modules/doc/doDont/doDont.ts +17 -0
  32. package/src/modules/doc/header/header.css +70 -37
  33. package/src/modules/doc/header/header.html +40 -134
  34. package/src/modules/doc/header/header.ts +29 -78
  35. package/src/modules/doc/heading/heading.css +33 -0
  36. package/src/modules/doc/heading/heading.html +14 -0
  37. package/src/modules/doc/heading/heading.ts +67 -0
  38. package/src/modules/doc/headingAnchor/headingAnchor.css +33 -0
  39. package/src/modules/doc/headingAnchor/headingAnchor.html +19 -0
  40. package/src/modules/doc/headingAnchor/headingAnchor.ts +43 -0
  41. package/src/modules/doc/headingContent/headingContent.css +53 -0
  42. package/src/modules/doc/headingContent/headingContent.html +13 -0
  43. package/src/modules/doc/headingContent/headingContent.ts +30 -0
  44. package/src/modules/doc/overview/overview.css +40 -0
  45. package/src/modules/doc/overview/overview.html +34 -0
  46. package/src/modules/doc/overview/overview.ts +12 -0
  47. package/src/modules/doc/phase/phase.css +70 -0
  48. package/src/modules/doc/phase/phase.html +38 -0
  49. package/src/modules/doc/phase/phase.ts +93 -0
  50. package/src/modules/doc/specificationContent/specificationContent.css +3 -0
  51. package/src/modules/doc/specificationContent/specificationContent.html +99 -0
  52. package/src/modules/doc/specificationContent/specificationContent.ts +56 -0
  53. package/src/modules/doc/sprigSurvey/sprigSurvey.html +20 -0
  54. package/src/modules/doc/sprigSurvey/sprigSurvey.scoped.css +16 -0
  55. package/src/modules/doc/sprigSurvey/sprigSurvey.ts +16 -0
  56. package/src/modules/doc/toc/toc.ts +1 -1
  57. package/src/modules/doc/versionPicker/versionPicker.css +64 -0
  58. package/src/modules/doc/versionPicker/versionPicker.html +38 -0
  59. package/src/modules/doc/versionPicker/versionPicker.ts +65 -0
  60. package/src/modules/doc/xmlContent/types.ts +120 -0
  61. package/src/modules/doc/xmlContent/utils.ts +163 -0
  62. package/src/modules/doc/xmlContent/xmlContent.css +54 -0
  63. package/src/modules/doc/xmlContent/xmlContent.html +52 -0
  64. package/src/modules/doc/xmlContent/xmlContent.ts +780 -0
  65. package/src/modules/docHelpers/amfStyle/amfStyle.css +355 -0
  66. package/src/modules/docHelpers/imgStyle/imgStyle.css +59 -0
  67. package/src/modules/docHelpers/status/status.css +22 -0
  68. package/src/modules/docUtils/searchSyncer/searchSyncer.ts +86 -0
  69. package/src/modules/docUtils/utils/utils.ts +32 -0
  70. package/LICENSE +0 -12
  71. package/src/modules/doc/content/__tests__/content.test.ts +0 -120
  72. package/src/modules/doc/content/__tests__/mockDocContent.ts +0 -292
  73. package/src/modules/doc/content/__tests__/mockPageReference.ts +0 -8
  74. package/src/modules/doc/content/content.stories.ts +0 -108
  75. package/src/modules/doc/contentCallout/__tests__/contentCallout.test.ts +0 -80
  76. package/src/modules/doc/contentCallout/__tests__/mockProps.ts +0 -14
  77. package/src/modules/doc/contentCallout/contentCallout.stories.ts +0 -29
  78. package/src/modules/doc/contentMedia/__tests__/contentMedia.test.ts +0 -97
  79. package/src/modules/doc/contentMedia/contentMedia.stories.ts +0 -113
  80. package/src/modules/doc/header/__tests__/coveoConfig.ts +0 -12
  81. package/src/modules/doc/header/__tests__/header.test.ts +0 -409
  82. package/src/modules/doc/header/__tests__/mockNavDevelopers.ts +0 -427
  83. package/src/modules/doc/header/__tests__/mockNavs.ts +0 -115
  84. package/src/modules/doc/header/__tests__/mockProps.ts +0 -149
  85. package/src/modules/doc/header/header.stories.ts +0 -160
  86. package/src/modules/doc/nav/__tests__/mockAvailableLanguages.ts +0 -8
  87. package/src/modules/doc/nav/__tests__/mockAvailableVersions.ts +0 -122
  88. package/src/modules/doc/nav/__tests__/mockPageReference.ts +0 -8
  89. package/src/modules/doc/nav/__tests__/mockPdfUrl.ts +0 -1
  90. package/src/modules/doc/nav/__tests__/mockSelectedLanguage.ts +0 -8
  91. package/src/modules/doc/nav/__tests__/mockSelectedVersion.ts +0 -8
  92. package/src/modules/doc/nav/__tests__/mockToc.ts +0 -146
  93. package/src/modules/doc/nav/__tests__/nav.test.ts +0 -58
  94. package/src/modules/doc/toc/__tests__/mockPageReference.ts +0 -8
  95. package/src/modules/doc/toc/__tests__/mockToc.ts +0 -146
  96. package/src/modules/doc/toc/__tests__/toc.test.ts +0 -29
  97. package/src/modules/doc/toolbar/__tests__/mockAvailableLanguages.ts +0 -8
  98. package/src/modules/doc/toolbar/__tests__/mockAvailableVersions.ts +0 -122
  99. package/src/modules/doc/toolbar/__tests__/mockPdfUrl.ts +0 -1
  100. package/src/modules/doc/toolbar/__tests__/mockSelectedLanguage.ts +0 -8
  101. package/src/modules/doc/toolbar/__tests__/mockSelectedVersion.ts +0 -8
  102. package/src/modules/doc/toolbar/__tests__/toolbar.test.ts +0 -44
@@ -0,0 +1,610 @@
1
+ /* eslint-disable @lwc/lwc/no-document-query */
2
+ import { LightningElement, api, track } from "lwc";
3
+ import { closest } from "kagekiri";
4
+ import { toJson } from "dxUtils/normalizers";
5
+ import { highlightTerms } from "dxUtils/highlight";
6
+ import { SearchSyncer } from "docUtils/searchSyncer";
7
+ import type { OptionWithLink } from "typings/custom";
8
+
9
+ type AnchorMap = { [key: string]: { intersect: boolean; id: string } };
10
+
11
+ declare const Sprig: (eventType: string, eventNme: string) => void;
12
+
13
+ const TOC_HEADER_TAG = "doc-heading";
14
+ const RNB_BY_TAB = "docs-tab";
15
+ const HIGHLIGHTABLE_SELECTOR = [
16
+ "p",
17
+ "h1",
18
+ "h2",
19
+ "h3",
20
+ "h4",
21
+ "h5",
22
+ "h6",
23
+ "li",
24
+ "dl",
25
+ "th",
26
+ "td"
27
+ ].join(",");
28
+ export const OBSERVER_ATTACH_WAIT_TIME = 500;
29
+
30
+ export default class ContentLayout extends LightningElement {
31
+ @api sidebarValue!: string;
32
+ @api sidebarHeader!: string;
33
+ @api tocTitle!: string;
34
+ @api enableSlotChange = false;
35
+ @api coveoOrganizationId!: string;
36
+ @api coveoPublicAccessToken!: string;
37
+ @api coveoAnalyticsToken!: string;
38
+ @api coveoSearchHub!: string;
39
+ @api coveoAdvancedQueryConfig!: string;
40
+ @api useOldSidebar?: boolean = false;
41
+ @api languages!: OptionWithLink[];
42
+ @api language!: string;
43
+ @api bailHref!: string;
44
+ @api bailLabel!: string;
45
+
46
+ // This is needed for now to prevent failing snapshot tests due to links in the footer
47
+ @api showFooter = false;
48
+
49
+ @api
50
+ get breadcrumbs() {
51
+ return this._breadcrumbs;
52
+ }
53
+
54
+ set breadcrumbs(value) {
55
+ if (value) {
56
+ this._breadcrumbs = toJson(value);
57
+ }
58
+ }
59
+
60
+ @api
61
+ get sidebarContent() {
62
+ return this._sidebarContent;
63
+ }
64
+
65
+ set sidebarContent(value: any) {
66
+ this._sidebarContent = toJson(value);
67
+ }
68
+
69
+ @api
70
+ get tocOptions() {
71
+ return this._tocOptions;
72
+ }
73
+
74
+ set tocOptions(value) {
75
+ this._tocOptions = toJson(value);
76
+ }
77
+
78
+ @api
79
+ setSidebarInputValue(searchTerm: string): void {
80
+ (this.template.querySelector("dx-sidebar") as any)?.setInputValue(
81
+ searchTerm
82
+ );
83
+ }
84
+
85
+ @track
86
+ private _sidebarContent: unknown;
87
+
88
+ private _breadcrumbs = null;
89
+
90
+ @track
91
+ private _tocOptions!: Array<unknown>;
92
+
93
+ private tocOptionIdsSet = new Set();
94
+ private anchoredElements: AnchorMap = {};
95
+ private lastScrollPosition!: number;
96
+ private observer?: IntersectionObserver;
97
+ private hasRendered: boolean = false;
98
+ private contentLoaded: boolean = false;
99
+ private sidebarOpen: boolean = false;
100
+ private rnbByTab: boolean = false;
101
+
102
+ get shouldDisplayFeedback() {
103
+ return this.contentLoaded && typeof Sprig !== "undefined";
104
+ }
105
+
106
+ private setRNBByTab() {
107
+ const tabPanelListItem: any = this.getTabPanelList();
108
+ this.rnbByTab = tabPanelListItem?.id === RNB_BY_TAB ? true : false;
109
+ }
110
+
111
+ get showTabBasedRNB() {
112
+ return this.rnbByTab;
113
+ }
114
+
115
+ onTabChanged = () => {
116
+ this.updateRNB();
117
+ };
118
+
119
+ private getTabPanelList() {
120
+ return document.querySelector("dx-tab-panel-list");
121
+ }
122
+
123
+ updateRNB = () => {
124
+ const headingElements = this.getHeadingElements();
125
+ headingElements.forEach((headingElement: any) => {
126
+ // Sometimes elements hash and header is not being set when slot content is wrapped with div
127
+ headingElement.hash = headingElement.attributes.hash?.nodeValue;
128
+ headingElement.header = headingElement.attributes.header?.nodeValue;
129
+ });
130
+ this.updateTocItems(headingElements);
131
+ };
132
+
133
+ private getHeadingElements() {
134
+ let headingElements = document.querySelectorAll(TOC_HEADER_TAG);
135
+ if (this.showTabBasedRNB) {
136
+ const tabPanelListItem: any = this.getTabPanelList();
137
+ const tabPanels =
138
+ tabPanelListItem?.querySelectorAll("dx-tab-panel");
139
+ for (const tabPanelItem of tabPanels) {
140
+ if (tabPanelItem.active) {
141
+ headingElements =
142
+ tabPanelItem.querySelectorAll(TOC_HEADER_TAG);
143
+ break;
144
+ }
145
+ }
146
+ }
147
+ return headingElements;
148
+ }
149
+
150
+ private updateURL() {
151
+ const tabs = this.getAllTabs();
152
+ const selectedTabId = this.getSelectedTabId();
153
+ tabs.forEach((tab: any) => {
154
+ if (tab.getAttribute("aria-selected") === "true") {
155
+ const tabID = tab.getAttribute("aria-label");
156
+ const url = new URL(window.location.href);
157
+ if (selectedTabId !== tabID) {
158
+ url.searchParams.set("type", tabID);
159
+ url.hash = "";
160
+ window.history.pushState({}, "", url.toString());
161
+ }
162
+ }
163
+ });
164
+ }
165
+
166
+ private searchSyncer = new SearchSyncer({
167
+ callbacks: {
168
+ onSearchChange: (nextSearchString: string): void => {
169
+ this.dispatchHighlightChange(
170
+ new URLSearchParams(nextSearchString).get("q") || ""
171
+ );
172
+ }
173
+ },
174
+ eventName: "sidebarsearchchange",
175
+ historyMethod: window.history.pushState,
176
+ searchParam: "q",
177
+ shouldStopPropagation: true,
178
+ target: window
179
+ });
180
+ private tocValue?: string = undefined;
181
+ // eslint-disable-next-line no-undef
182
+ private observerTimerId?: NodeJS.Timeout;
183
+ private didScrollToSelectedHash = false;
184
+ private _scrollInterval = 0;
185
+
186
+ get showToc(): boolean {
187
+ return this.tocOptions && this.tocOptions.length > 0;
188
+ }
189
+
190
+ // ? This could be a good default for pathname in dx-breadcrumbs. Using this getter for now as a workaround.
191
+ get pathname(): string {
192
+ return window.location.pathname;
193
+ }
194
+
195
+ get showBreadcrumbs(): boolean {
196
+ return (
197
+ this.breadcrumbs != null && (this.breadcrumbs as any[]).length > 1
198
+ );
199
+ }
200
+
201
+ // This event gets triggered when navigating back/forward
202
+ handlePopState = (): void => {
203
+ if (this.showTabBasedRNB) {
204
+ this.restoreTabSelection();
205
+ }
206
+ };
207
+
208
+ connectedCallback(): void {
209
+ const hasParentHighlightListener = closest(
210
+ "doc-xml-content",
211
+ this.template.host
212
+ );
213
+ if (!hasParentHighlightListener) {
214
+ window.addEventListener(
215
+ "highlightedtermchange",
216
+ this.updateHighlighted
217
+ );
218
+ this.searchSyncer.init();
219
+ }
220
+ window.addEventListener("popstate", this.handlePopState);
221
+ }
222
+
223
+ private getSelectedTabId() {
224
+ const urlParams = new URLSearchParams(window.location.search);
225
+ const selectedTabId = urlParams.get("type");
226
+ return selectedTabId;
227
+ }
228
+
229
+ private restoreTabSelection() {
230
+ requestAnimationFrame(() => {
231
+ const selectedTabId = this.getSelectedTabId();
232
+ if (selectedTabId) {
233
+ this.selectTabById(selectedTabId);
234
+ }
235
+ });
236
+ }
237
+
238
+ private getAllTabs(): any[] {
239
+ const tabPanelListItem: any = this.getTabPanelList();
240
+ if (tabPanelListItem?.shadowRoot) {
241
+ return Array.from(
242
+ tabPanelListItem.shadowRoot.querySelectorAll(
243
+ "dx-tab-panel-item"
244
+ )
245
+ ).map((tabPanelItem: any) =>
246
+ tabPanelItem.shadowRoot.querySelector("button")
247
+ );
248
+ }
249
+ return [];
250
+ }
251
+
252
+ private selectTabById(tabId: string) {
253
+ const tabs = this.getAllTabs();
254
+ tabs.forEach((tab: any) => {
255
+ if (tab.getAttribute("aria-label") === tabId) {
256
+ tab.click();
257
+ }
258
+ });
259
+ }
260
+
261
+ renderedCallback(): void {
262
+ /**
263
+ * Note: We are adding timeout because chrome is optimizing and not triggering recent renderedCallback though elements reference is changed
264
+ * Also we are considering recent renderedCallback
265
+ */
266
+ this.clearRenderObserverTimer();
267
+ this.observerTimerId = setTimeout(
268
+ this.attachInteractionObserver,
269
+ OBSERVER_ATTACH_WAIT_TIME
270
+ );
271
+
272
+ this.adjustNavPosition();
273
+ window.addEventListener("scroll", this.adjustNavPosition);
274
+ window.addEventListener("resize", this.adjustNavPosition);
275
+
276
+ if (!this.hasRendered) {
277
+ this.hasRendered = true;
278
+ this.setRNBByTab();
279
+ if (this.showTabBasedRNB) {
280
+ window.addEventListener("tabchanged", this.onTabChanged);
281
+ this.restoreTabSelection();
282
+ }
283
+ this.restoreScroll();
284
+ }
285
+ }
286
+
287
+ disconnectedCallback(): void {
288
+ this.disconnectObserver();
289
+ window.removeEventListener(
290
+ "highlightedtermchange",
291
+ this.updateHighlighted
292
+ );
293
+ window.removeEventListener("scroll", this.adjustNavPosition);
294
+ window.removeEventListener("resize", this.adjustNavPosition);
295
+ window.removeEventListener("tabchanged", this.onTabChanged);
296
+ window.removeEventListener("popstate", this.handlePopState);
297
+ this.searchSyncer.dispose();
298
+ this.clearRenderObserverTimer();
299
+
300
+ window.clearInterval(this._scrollInterval);
301
+ }
302
+
303
+ restoreScroll() {
304
+ document.body.scrollTop = document.documentElement.scrollTop =
305
+ window.history.state?.scrollValue;
306
+ }
307
+
308
+ clearRenderObserverTimer = () => {
309
+ if (this.observerTimerId) {
310
+ clearTimeout(this.observerTimerId);
311
+ }
312
+ };
313
+
314
+ /*
315
+ This is a workaround for the global nav sticky header being decoupled from the doc header & doc phase.
316
+ We have to account for the global nav changing height due to animations.
317
+ */
318
+ adjustNavPosition = () => {
319
+ const sidebarType = this.useOldSidebar
320
+ ? "dx-sidebar-old"
321
+ : "dx-sidebar";
322
+ const sidebarEl = this.template.querySelector(sidebarType);
323
+ const globalNavEl = document.querySelector(
324
+ "hgf-c360nav"
325
+ ) as HTMLElement;
326
+ const contextNavEl = document.querySelector(
327
+ "hgf-c360contextnav"
328
+ ) as HTMLElement;
329
+ const docHeaderEl = document.querySelector(
330
+ ".sticky-doc-header"
331
+ ) as HTMLElement;
332
+
333
+ let docPhaseEl = (
334
+ this.template.querySelector("[name=doc-phase]")! as any
335
+ ).assignedElements()[0] as HTMLSlotElement;
336
+
337
+ if (!docPhaseEl) {
338
+ docPhaseEl = (
339
+ this.template.querySelector("[name=version-banner]")! as any
340
+ ).assignedElements()[0] as HTMLSlotElement;
341
+ }
342
+
343
+ if (!sidebarEl || !globalNavEl || !contextNavEl || !docHeaderEl) {
344
+ console.warn("One or more required elements are missing.");
345
+ return;
346
+ }
347
+
348
+ // sync with the browser to account for any reflows that may have happened
349
+ requestAnimationFrame(() => {
350
+ // ternary is a temporary fix for the global nav height reporting incorrectly on some browsers
351
+ const globalNavHeight =
352
+ (globalNavEl.getBoundingClientRect().height !== 72 ? 0 : 72) +
353
+ contextNavEl.getBoundingClientRect().height;
354
+ const docHeaderHeight = docHeaderEl.getBoundingClientRect().height;
355
+ const totalHeaderHeight = globalNavHeight + docHeaderHeight;
356
+
357
+ // Selecting the doc section heading and RNB here.
358
+ const docHeadingEls = Array.from(
359
+ document.querySelectorAll("doc-heading")
360
+ );
361
+ const rightNavBarEl = this.template.querySelector(".right-nav-bar");
362
+
363
+ sidebarEl.style.setProperty(
364
+ "--dx-c-content-sidebar-sticky-top",
365
+ `${globalNavHeight + docHeaderHeight}px`
366
+ );
367
+
368
+ docHeaderEl.style.setProperty(
369
+ "--dx-g-global-header-height",
370
+ `${globalNavHeight}px`
371
+ );
372
+
373
+ // Adjusting the doc section heading on scroll.
374
+ docHeadingEls.forEach((docHeadingEl) => {
375
+ (docHeadingEl as any).style.scrollMarginTop = docPhaseEl
376
+ ? `${
377
+ totalHeaderHeight +
378
+ docPhaseEl.getBoundingClientRect().height +
379
+ 40
380
+ }px`
381
+ : `${totalHeaderHeight + 40}px`;
382
+ });
383
+
384
+ // Adjusting the right nav bar on scroll.
385
+ if (rightNavBarEl) {
386
+ rightNavBarEl.style.top = docPhaseEl
387
+ ? `${
388
+ totalHeaderHeight +
389
+ docPhaseEl.getBoundingClientRect().height
390
+ }px`
391
+ : `${totalHeaderHeight}px`;
392
+ }
393
+
394
+ // If doc phase element exists, we need to account for its sticky position. Mobile should include the sidebar height (since it becomes sticky aswell).
395
+ if (docPhaseEl) {
396
+ docPhaseEl.style.setProperty(
397
+ "--doc-c-phase-top",
398
+ `${
399
+ window.innerWidth < 769
400
+ ? globalNavHeight +
401
+ docHeaderHeight +
402
+ sidebarEl.getBoundingClientRect().height
403
+ : globalNavHeight + docHeaderHeight
404
+ }px`
405
+ );
406
+ }
407
+ });
408
+ };
409
+
410
+ updateHighlighted = (event: Event): void =>
411
+ highlightTerms(
412
+ this.querySelectorAll(HIGHLIGHTABLE_SELECTOR),
413
+ (event as CustomEvent<string>).detail
414
+ );
415
+
416
+ attachInteractionObserver = (): void => {
417
+ if (!this.enableSlotChange) {
418
+ return;
419
+ }
420
+ this.disconnectObserver();
421
+
422
+ const globalNavOffset = `-${getComputedStyle(
423
+ document.documentElement
424
+ ).getPropertyValue("--dx-g-doc-header-main-nav-height")}`;
425
+
426
+ this.observer = new IntersectionObserver(
427
+ (entries) => {
428
+ entries.forEach(
429
+ (entry) =>
430
+ (this.anchoredElements[
431
+ entry.target.getAttribute("id")!
432
+ ].intersect = entry.isIntersecting)
433
+ );
434
+ this.calculateActualSection();
435
+ },
436
+ {
437
+ rootMargin: globalNavOffset.trim()
438
+ }
439
+ );
440
+
441
+ // Note: We are doing document.querySelectorAll as a quick fix as we are not getting heading elements reference this.querySelectorAll
442
+ const headingElements = this.getHeadingElements();
443
+
444
+ // We only need to update URL in case of /docs and ignore if tabs are used anywhere else in DSC
445
+ if (this.showTabBasedRNB) {
446
+ this.updateURL();
447
+ }
448
+
449
+ for (const headingElement of headingElements as any) {
450
+ // Add headingElements to intersectionObserver for highlighting respective RNB item when user scroll
451
+ const id = headingElement.getAttribute("id")!;
452
+ this.anchoredElements[id] = {
453
+ id,
454
+ intersect: false
455
+ };
456
+ this.observer.observe(headingElement);
457
+ }
458
+
459
+ if (!this.didScrollToSelectedHash) {
460
+ this.didScrollToSelectedHash = true;
461
+ this.scrollToHash(headingElements);
462
+ }
463
+ };
464
+
465
+ onSlotChange(): void {
466
+ this.updateRNB();
467
+ this.contentLoaded = true;
468
+ }
469
+
470
+ // eslint-disable-next-line no-undef
471
+ private updateTocItems(headingElements: NodeListOf<Element>): void {
472
+ const tocOptions = [];
473
+
474
+ for (const headingElement of headingElements as any) {
475
+ headingElement.id = headingElement.hash;
476
+
477
+ // Update tocOptions from anchorTags only for H2, consider default as 2 as per component
478
+ const headingAriaLevel =
479
+ headingElement.attributes["aria-level"]?.nodeValue || "2";
480
+ const isH2 = headingAriaLevel === "2";
481
+
482
+ if (isH2) {
483
+ const tocItem = {
484
+ anchor: `#${headingElement.hash}`,
485
+ id: headingElement.id,
486
+ label: headingElement.header
487
+ };
488
+ tocOptions.push(tocItem);
489
+ this.tocOptionIdsSet.add(headingElement.id);
490
+ }
491
+ }
492
+
493
+ this._tocOptions = tocOptions;
494
+ }
495
+
496
+ private disconnectObserver(): void {
497
+ if (this.observer) {
498
+ this.observer.disconnect();
499
+ this.observer = undefined;
500
+ }
501
+ }
502
+
503
+ // eslint-disable-next-line no-undef
504
+ private scrollToHash(headingElements: NodeListOf<Element>): void {
505
+ let { hash } = window.location;
506
+ if (hash) {
507
+ hash = hash.substr(1);
508
+
509
+ const docHeaderEl = document.querySelector(
510
+ ".sticky-doc-header"
511
+ ) as HTMLElement;
512
+ const globalNavEl = document.querySelector(
513
+ "hgf-c360nav"
514
+ ) as HTMLElement;
515
+ const contextNavEl = document.querySelector(
516
+ "hgf-c360contextnav"
517
+ ) as HTMLElement;
518
+
519
+ const headerHeight =
520
+ docHeaderEl?.offsetHeight +
521
+ globalNavEl?.offsetHeight +
522
+ contextNavEl?.offsetHeight;
523
+
524
+ const docPhaseEl = (
525
+ this.template.querySelector("[name=doc-phase]")! as any
526
+ ).assignedElements()[0] as HTMLSlotElement;
527
+
528
+ const offset = docPhaseEl
529
+ ? headerHeight + docPhaseEl.offsetHeight
530
+ : headerHeight;
531
+
532
+ for (const headingElement of headingElements as any) {
533
+ if (headingElement.getAttribute("id") === hash) {
534
+ this.scrollIntoViewWithOffset(headingElement, offset);
535
+ break;
536
+ }
537
+ }
538
+ }
539
+ }
540
+
541
+ private scrollIntoViewWithOffset(
542
+ headingElement: HTMLElement,
543
+ offset: number
544
+ ) {
545
+ window.scrollTo({
546
+ behavior: "auto",
547
+ top:
548
+ headingElement.getBoundingClientRect().top -
549
+ document.body.getBoundingClientRect().top -
550
+ offset
551
+ });
552
+ }
553
+
554
+ private calculateActualSection(): void {
555
+ const currentScrollPosition = document.documentElement.scrollTop;
556
+ const id = Object.keys(this.anchoredElements).find(
557
+ (_id) => this.anchoredElements[_id].intersect
558
+ );
559
+ if (id) {
560
+ this.assignElementId(id);
561
+ } else if (currentScrollPosition < this.lastScrollPosition) {
562
+ // The user has scroll up since last update
563
+ this.assignElementId(this.calculatePreviousElementId());
564
+ }
565
+
566
+ this.lastScrollPosition = currentScrollPosition;
567
+ }
568
+
569
+ private calculatePreviousElementId(): string | undefined {
570
+ const keys = Object.keys(this.anchoredElements);
571
+ const currentIndex = keys.findIndex((id) => this.tocValue === id);
572
+
573
+ return currentIndex > 0 ? keys[currentIndex - 1] : undefined;
574
+ }
575
+
576
+ private assignElementId(id: string | undefined): void {
577
+ // Change toc(RNB) highlight only for H2
578
+ if (this.tocOptionIdsSet.has(id)) {
579
+ this.tocValue = id;
580
+ }
581
+ }
582
+
583
+ private dispatchHighlightChange(term: string): void {
584
+ this.dispatchEvent(
585
+ new CustomEvent("highlightedtermchange", {
586
+ detail: term,
587
+ bubbles: true,
588
+ composed: true
589
+ })
590
+ );
591
+ }
592
+
593
+ private updateHighlightsAndSearch(nextSearchString: string): void {
594
+ const nextSearchParam =
595
+ new URLSearchParams(nextSearchString).get("q") || "";
596
+ this.setSidebarInputValue(nextSearchParam);
597
+ this.dispatchHighlightChange(nextSearchParam);
598
+ }
599
+
600
+ private onToggleSidebar(e: CustomEvent): void {
601
+ this.sidebarOpen = e.detail.open;
602
+
603
+ // eslint-disable-next-line @lwc/lwc/no-document-query
604
+ const footer = document.querySelector("dx-footer") as HTMLElement;
605
+
606
+ if (footer) {
607
+ footer.style.display = this.sidebarOpen ? "none" : "block";
608
+ }
609
+ }
610
+ }
@@ -0,0 +1,47 @@
1
+ @import "dxHelpers/reset";
2
+ @import "dxHelpers/text";
3
+
4
+ .container {
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: var(--dx-g-spacing-md);
8
+ flex: 1;
9
+ }
10
+
11
+ .doc-do-dont-header {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: var(--dx-g-spacing-sm);
15
+ }
16
+
17
+ .doc-do-dont-label {
18
+ font-family: var(--dx-g-font-display);
19
+ font-size: var(--dx-g-spacing-md);
20
+ font-weight: var(--dx-g-font-demi);
21
+ line-height: var(--dx-g-spacing-lg);
22
+ }
23
+
24
+ .doc-do-color {
25
+ color: var(--dx-g-green-vibrant-50);
26
+ }
27
+
28
+ .doc-dont-color {
29
+ color: var(--dx-g-red-vibrant-30);
30
+ }
31
+
32
+ .do-dont-image-container {
33
+ display: flex;
34
+ max-height: 480px;
35
+ min-height: 140px;
36
+ padding: var(--dx-g-spacing-3xl) var(--dx-g-spacing-2xl);
37
+ flex-direction: column;
38
+ justify-content: center;
39
+ align-items: center;
40
+ flex: 1;
41
+ border-radius: var(--dx-g-spacing-sm);
42
+ border: 1px solid var(--dx-g-brand-default-color-border-2);
43
+ }
44
+
45
+ .doc-do-dont-img {
46
+ object-fit: contain;
47
+ }
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <div class="container">
3
+ <div class="doc-do-dont-header">
4
+ <template lwc:if={isDo}>
5
+ <dx-icon
6
+ symbol="success"
7
+ size="large"
8
+ color="green-vibrant-50"
9
+ ></dx-icon>
10
+ <div class="doc-do-dont-label doc-do-color">Do</div>
11
+ </template>
12
+ <template lwc:else>
13
+ <dx-icon
14
+ symbol="clear"
15
+ size="large"
16
+ color="red-vibrant-30"
17
+ class="doc-do-dont-icon"
18
+ ></dx-icon>
19
+ <div class="doc-do-dont-label doc-dont-color">Don't</div>
20
+ </template>
21
+ </div>
22
+ <div class="do-dont-image-container">
23
+ <img class="doc-do-dont-img" src={imgSrc} alt={caption} />
24
+ </div>
25
+ <div class="dx-text-body-4">{caption}</div>
26
+ </div>
27
+ </template>
@@ -0,0 +1,17 @@
1
+ import { LightningElement, api } from "lwc";
2
+ import { normalizeBoolean } from "dxUtils/normalizers";
3
+
4
+ export default class DoDont extends LightningElement {
5
+ @api caption: string = "";
6
+ @api imgSrc!: string;
7
+ _isDo: boolean = false;
8
+
9
+ @api
10
+ get isDo(): boolean {
11
+ return this._isDo;
12
+ }
13
+
14
+ set isDo(value) {
15
+ this._isDo = normalizeBoolean(value);
16
+ }
17
+ }