@salesforcedevs/docs-components 1.3.376 → 1.3.387

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lwc.config.json CHANGED
@@ -13,11 +13,13 @@
13
13
  "doc/contentLayout",
14
14
  "doc/contentMedia",
15
15
  "doc/docXmlContent",
16
+ "doc/lwcContentLayout",
16
17
  "doc/header",
17
18
  "doc/heading",
18
19
  "doc/headingAnchor",
19
20
  "doc/overview",
20
21
  "doc/phase",
22
+ "doc/specificationContent",
21
23
  "doc/versionPicker",
22
24
  "doc/xmlContent",
23
25
  "docUtils/utils"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/docs-components",
3
- "version": "1.3.376",
3
+ "version": "1.3.387",
4
4
  "description": "Docs Lightning web components for DSC",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
@@ -24,5 +24,5 @@
24
24
  "@types/lodash.orderby": "4.6.9",
25
25
  "@types/lodash.uniqby": "4.7.9"
26
26
  },
27
- "gitHead": "1f15d560a49d28943329fca9c8cff12aa9edaf71"
27
+ "gitHead": "5181a287c393b41fe90d9e23c054421eaa1d2e72"
28
28
  }
@@ -1,131 +1 @@
1
- :host {
2
- --dx-c-content-vertical-spacing: var(--dx-g-spacing-lg);
3
- --dx-c-content-sidebar-sticky-top: calc(
4
- var(--dx-g-global-header-height) + var(--dx-g-doc-header-height)
5
- );
6
- --dx-c-sidebar-height: calc(
7
- 100vh -
8
- calc(
9
- var(--dx-g-global-header-height) + var(--dx-g-doc-header-height)
10
- )
11
- );
12
- --dx-c-content-scroll-margin-top: calc(
13
- var(--dx-g-global-header-height) + var(--dx-g-doc-header-height) +
14
- var(--dx-g-spacing-2xl)
15
- );
16
-
17
- display: block;
18
- }
19
-
20
- doc-breadcrumbs {
21
- --dx-c-popover-z-index: 5;
22
-
23
- display: block;
24
- margin-bottom: var(--dx-g-spacing-2xl);
25
- }
26
-
27
- dx-sidebar,
28
- dx-sidebar-old {
29
- --dx-c-sidebar-vertical-padding: var(--dx-g-spacing-md);
30
-
31
- z-index: calc(var(--dx-g-z-index-100) + 5);
32
- }
33
-
34
- dx-toc {
35
- --dx-c-toc-width: unset;
36
-
37
- height: calc(100% - var(--dx-c-content-vertical-spacing) * 2);
38
- margin: var(--dx-c-content-vertical-spacing) 0;
39
- overflow-y: auto;
40
- }
41
-
42
- dx-sidebar,
43
- dx-toc {
44
- display: block;
45
- }
46
-
47
- /* offset page jump link due to fixed header */
48
- ::slotted(doc-heading) {
49
- scroll-margin-top: var(--dx-c-content-scroll-margin-top);
50
- }
51
-
52
- .content {
53
- display: flex;
54
- position: relative;
55
- }
56
-
57
- .content-body-doc-phase-container {
58
- flex: 1;
59
- border-left: 1px solid var(--dx-g-gray-90);
60
- }
61
-
62
- .content-body-container {
63
- display: flex;
64
- flex-direction: row;
65
- justify-content: center;
66
- max-width: var(--dx-g-doc-content-max-width);
67
-
68
- /* Derived this manually by substracting (topHeader, doc header, banner and the content). */
69
- min-height: 62vh;
70
- margin: auto;
71
- padding: 0 var(--dx-g-global-header-padding-horizontal);
72
- margin-bottom: calc(2 * (var(--dx-g-spacing-5xl) + 4px));
73
- }
74
-
75
- .content-body {
76
- margin: var(--dx-g-spacing-md) 0 0;
77
- max-width: 900px;
78
- flex: 1;
79
- width: 0;
80
- }
81
-
82
- .is-sticky {
83
- align-self: flex-start;
84
- position: sticky;
85
- top: var(--dx-c-content-sidebar-sticky-top);
86
- }
87
-
88
- .right-nav-bar {
89
- margin-left: var(--dx-g-spacing-2xl);
90
- }
91
-
92
- @media screen and (max-width: 1024px) {
93
- .right-nav-bar {
94
- display: none;
95
- }
96
- }
97
-
98
- @media screen and (max-width: 800px) {
99
- .content-body {
100
- margin-top: var(--dx-c-content-vertical-spacing);
101
- }
102
-
103
- .content-body-doc-phase-container {
104
- border-left: 0;
105
- }
106
- }
107
-
108
- @media screen and (max-width: 768px) {
109
- .is-sticky {
110
- width: 100%;
111
- }
112
-
113
- .content {
114
- flex-direction: column;
115
- }
116
-
117
- .content-body-container {
118
- padding-right: 0;
119
- overflow-x: auto;
120
- margin-bottom: calc(var(--dx-g-spacing-5xl) + 4px);
121
- }
122
-
123
- .left-nav-bar {
124
- height: unset;
125
- }
126
-
127
- .content-body {
128
- margin-left: var(--dx-g-spacing-mlg, 20px);
129
- margin-right: var(--dx-g-spacing-mlg, 20px);
130
- }
131
- }
1
+ @import "docHelpers/contentLayoutStyle";
@@ -11,7 +11,7 @@ type AnchorMap = { [key: string]: { intersect: boolean; id: string } };
11
11
  declare const Sprig: (eventType: string, eventNme: string) => void;
12
12
 
13
13
  const TOC_HEADER_TAG = "doc-heading";
14
- const RNB_BY_TAB = "docs-tab";
14
+
15
15
  const HIGHLIGHTABLE_SELECTOR = [
16
16
  "p",
17
17
  "h1",
@@ -83,87 +83,26 @@ export default class ContentLayout extends LightningElement {
83
83
  }
84
84
 
85
85
  @track
86
- private _sidebarContent: unknown;
86
+ protected _sidebarContent: unknown;
87
87
 
88
- private _breadcrumbs = null;
88
+ protected _breadcrumbs = null;
89
89
 
90
90
  @track
91
- private _tocOptions!: Array<unknown>;
91
+ protected _tocOptions!: Array<unknown>;
92
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;
93
+ protected tocOptionIdsSet = new Set();
94
+ protected anchoredElements: AnchorMap = {};
95
+ protected lastScrollPosition!: number;
96
+ protected observer?: IntersectionObserver;
97
+ protected hasRendered: boolean = false;
98
+ protected contentLoaded: boolean = false;
99
+ protected sidebarOpen: boolean = false;
101
100
 
102
101
  get shouldDisplayFeedback() {
103
102
  return this.contentLoaded && typeof Sprig !== "undefined";
104
103
  }
105
104
 
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({
105
+ protected searchSyncer = new SearchSyncer({
167
106
  callbacks: {
168
107
  onSearchChange: (nextSearchString: string): void => {
169
108
  this.dispatchHighlightChange(
@@ -177,11 +116,11 @@ export default class ContentLayout extends LightningElement {
177
116
  shouldStopPropagation: true,
178
117
  target: window
179
118
  });
180
- private tocValue?: string = undefined;
119
+ protected tocValue?: string = undefined;
181
120
  // eslint-disable-next-line no-undef
182
- private observerTimerId?: NodeJS.Timeout;
183
- private didScrollToSelectedHash = false;
184
- private _scrollInterval = 0;
121
+ protected observerTimerId?: NodeJS.Timeout;
122
+ protected didScrollToSelectedHash = false;
123
+ protected _scrollInterval = 0;
185
124
 
186
125
  get showToc(): boolean {
187
126
  return this.tocOptions && this.tocOptions.length > 0;
@@ -198,13 +137,6 @@ export default class ContentLayout extends LightningElement {
198
137
  );
199
138
  }
200
139
 
201
- // This event gets triggered when navigating back/forward
202
- handlePopState = (): void => {
203
- if (this.showTabBasedRNB) {
204
- this.restoreTabSelection();
205
- }
206
- };
207
-
208
140
  connectedCallback(): void {
209
141
  const hasParentHighlightListener = closest(
210
142
  "doc-xml-content",
@@ -217,46 +149,10 @@ export default class ContentLayout extends LightningElement {
217
149
  );
218
150
  this.searchSyncer.init();
219
151
  }
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
152
  }
251
153
 
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
- }
154
+ // Placeholder for childs renderedCallback
155
+ protected postRenderedCallback?(): void;
260
156
 
261
157
  renderedCallback(): void {
262
158
  /**
@@ -275,12 +171,10 @@ export default class ContentLayout extends LightningElement {
275
171
 
276
172
  if (!this.hasRendered) {
277
173
  this.hasRendered = true;
278
- this.setRNBByTab();
279
- if (this.showTabBasedRNB) {
280
- window.addEventListener("tabchanged", this.onTabChanged);
281
- this.restoreTabSelection();
282
- }
283
174
  this.restoreScroll();
175
+
176
+ // Dynamically call `renderedCallbackForLwcContentLayout` if it exists
177
+ this.postRenderedCallback?.();
284
178
  }
285
179
  }
286
180
 
@@ -292,8 +186,6 @@ export default class ContentLayout extends LightningElement {
292
186
  );
293
187
  window.removeEventListener("scroll", this.adjustNavPosition);
294
188
  window.removeEventListener("resize", this.adjustNavPosition);
295
- window.removeEventListener("tabchanged", this.onTabChanged);
296
- window.removeEventListener("popstate", this.handlePopState);
297
189
  this.searchSyncer.dispose();
298
190
  this.clearRenderObserverTimer();
299
191
 
@@ -413,6 +305,34 @@ export default class ContentLayout extends LightningElement {
413
305
  (event as CustomEvent<string>).detail
414
306
  );
415
307
 
308
+ protected getHeadingElements() {
309
+ // Note: We are doing document.querySelectorAll as a quick fix as we are not getting heading elements reference this.querySelectorAll
310
+ const headingElements = document.querySelectorAll(TOC_HEADER_TAG);
311
+ return headingElements;
312
+ }
313
+
314
+ updateHeadingForRNB(): void {
315
+ const headingElements = this.getHeadingElements();
316
+ this.addObserverAndScroll(headingElements);
317
+ }
318
+
319
+ addObserverAndScroll(headingElements: any) {
320
+ for (const headingElement of headingElements as any) {
321
+ // Add headingElements to intersectionObserver for highlighting respective RNB item when user scroll
322
+ const id = headingElement.getAttribute("id")!;
323
+ this.anchoredElements[id] = {
324
+ id,
325
+ intersect: false
326
+ };
327
+ this.observer?.observe(headingElement);
328
+ }
329
+
330
+ if (!this.didScrollToSelectedHash) {
331
+ this.didScrollToSelectedHash = true;
332
+ this.scrollToHash(headingElements);
333
+ }
334
+ }
335
+
416
336
  attachInteractionObserver = (): void => {
417
337
  if (!this.enableSlotChange) {
418
338
  return;
@@ -437,38 +357,11 @@ export default class ContentLayout extends LightningElement {
437
357
  rootMargin: globalNavOffset.trim()
438
358
  }
439
359
  );
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
- }
360
+ this.updateHeadingForRNB();
463
361
  };
464
362
 
465
- onSlotChange(): void {
466
- this.updateRNB();
467
- this.contentLoaded = true;
468
- }
469
-
470
363
  // eslint-disable-next-line no-undef
471
- private updateTocItems(headingElements: NodeListOf<Element>): void {
364
+ updateTocItems(headingElements: any): void {
472
365
  const tocOptions = [];
473
366
 
474
367
  for (const headingElement of headingElements as any) {
@@ -493,7 +386,33 @@ export default class ContentLayout extends LightningElement {
493
386
  this._tocOptions = tocOptions;
494
387
  }
495
388
 
496
- private disconnectObserver(): void {
389
+ setHashAndHeaderForDocHeading(headingElements: any) {
390
+ for (const headingElement of headingElements as any) {
391
+ // Sometimes elements hash and header is not being set when slot content is wrapped with div
392
+ if (!headingElement.hash) {
393
+ headingElement.hash = headingElement.attributes.hash?.nodeValue;
394
+ }
395
+
396
+ if (!headingElement.header) {
397
+ headingElement.header =
398
+ headingElement.attributes.header?.nodeValue;
399
+ }
400
+ }
401
+
402
+ this.updateTocItems(headingElements);
403
+ }
404
+
405
+ updateRNB = () => {
406
+ const headingElements = this.getHeadingElements();
407
+ this.setHashAndHeaderForDocHeading(headingElements);
408
+ };
409
+
410
+ onSlotChange(): void {
411
+ this.updateRNB();
412
+ this.contentLoaded = true;
413
+ }
414
+
415
+ protected disconnectObserver(): void {
497
416
  if (this.observer) {
498
417
  this.observer.disconnect();
499
418
  this.observer = undefined;
@@ -501,7 +420,7 @@ export default class ContentLayout extends LightningElement {
501
420
  }
502
421
 
503
422
  // eslint-disable-next-line no-undef
504
- private scrollToHash(headingElements: NodeListOf<Element>): void {
423
+ protected scrollToHash(headingElements: NodeListOf<Element>): void {
505
424
  let { hash } = window.location;
506
425
  if (hash) {
507
426
  hash = hash.substr(1);
@@ -538,7 +457,7 @@ export default class ContentLayout extends LightningElement {
538
457
  }
539
458
  }
540
459
 
541
- private scrollIntoViewWithOffset(
460
+ protected scrollIntoViewWithOffset(
542
461
  headingElement: HTMLElement,
543
462
  offset: number
544
463
  ) {
@@ -551,7 +470,7 @@ export default class ContentLayout extends LightningElement {
551
470
  });
552
471
  }
553
472
 
554
- private calculateActualSection(): void {
473
+ protected calculateActualSection(): void {
555
474
  const currentScrollPosition = document.documentElement.scrollTop;
556
475
  const id = Object.keys(this.anchoredElements).find(
557
476
  (_id) => this.anchoredElements[_id].intersect
@@ -566,21 +485,21 @@ export default class ContentLayout extends LightningElement {
566
485
  this.lastScrollPosition = currentScrollPosition;
567
486
  }
568
487
 
569
- private calculatePreviousElementId(): string | undefined {
488
+ protected calculatePreviousElementId(): string | undefined {
570
489
  const keys = Object.keys(this.anchoredElements);
571
490
  const currentIndex = keys.findIndex((id) => this.tocValue === id);
572
491
 
573
492
  return currentIndex > 0 ? keys[currentIndex - 1] : undefined;
574
493
  }
575
494
 
576
- private assignElementId(id: string | undefined): void {
495
+ protected assignElementId(id: string | undefined): void {
577
496
  // Change toc(RNB) highlight only for H2
578
497
  if (this.tocOptionIdsSet.has(id)) {
579
498
  this.tocValue = id;
580
499
  }
581
500
  }
582
501
 
583
- private dispatchHighlightChange(term: string): void {
502
+ protected dispatchHighlightChange(term: string): void {
584
503
  this.dispatchEvent(
585
504
  new CustomEvent("highlightedtermchange", {
586
505
  detail: term,
@@ -590,14 +509,14 @@ export default class ContentLayout extends LightningElement {
590
509
  );
591
510
  }
592
511
 
593
- private updateHighlightsAndSearch(nextSearchString: string): void {
512
+ protected updateHighlightsAndSearch(nextSearchString: string): void {
594
513
  const nextSearchParam =
595
514
  new URLSearchParams(nextSearchString).get("q") || "";
596
515
  this.setSidebarInputValue(nextSearchParam);
597
516
  this.dispatchHighlightChange(nextSearchParam);
598
517
  }
599
518
 
600
- private onToggleSidebar(e: CustomEvent): void {
519
+ protected onToggleSidebar(e: CustomEvent): void {
601
520
  this.sidebarOpen = e.detail.open;
602
521
 
603
522
  // eslint-disable-next-line @lwc/lwc/no-document-query
@@ -0,0 +1 @@
1
+ @import "docHelpers/contentLayoutStyle";
@@ -0,0 +1,64 @@
1
+ <template>
2
+ <div class="content">
3
+ <template lwc:if={useOldSidebar}>
4
+ <dx-sidebar-old
5
+ class="is-sticky left-nav-bar"
6
+ trees={sidebarContent}
7
+ value={sidebarValue}
8
+ header={sidebarHeader}
9
+ ontogglesidebar={onToggleSidebar}
10
+ languages={languages}
11
+ language={language}
12
+ bail-href={bailHref}
13
+ bail-label={bailLabel}
14
+ >
15
+ <slot name="sidebar-header" slot="version-picker"></slot>
16
+ </dx-sidebar-old>
17
+ </template>
18
+ <template lwc:else>
19
+ <dx-sidebar
20
+ class="is-sticky left-nav-bar"
21
+ trees={sidebarContent}
22
+ value={sidebarValue}
23
+ header={sidebarHeader}
24
+ coveo-organization-id={coveoOrganizationId}
25
+ coveo-public-access-token={coveoPublicAccessToken}
26
+ coveo-search-hub={coveoSearchHub}
27
+ coveo-advanced-query-config={coveoAdvancedQueryConfig}
28
+ ontogglesidebar={onToggleSidebar}
29
+ languages={languages}
30
+ language={language}
31
+ bail-href={bailHref}
32
+ bail-label={bailLabel}
33
+ >
34
+ <slot name="sidebar-header" slot="version-picker"></slot>
35
+ </dx-sidebar>
36
+ </template>
37
+ <div class="content-body-doc-phase-container">
38
+ <slot name="doc-phase"></slot>
39
+ <slot name="version-banner"></slot>
40
+ <div class="content-body-container">
41
+ <div class="content-body">
42
+ <doc-breadcrumbs
43
+ lwc:if={showBreadcrumbs}
44
+ breadcrumbs={breadcrumbs}
45
+ ></doc-breadcrumbs>
46
+ <slot onslotchange={onSlotChange}></slot>
47
+ <doc-sprig-survey
48
+ lwc:if={shouldDisplayFeedback}
49
+ ></doc-sprig-survey>
50
+ </div>
51
+ <div lwc:if={showToc} class="right-nav-bar is-sticky">
52
+ <dx-toc
53
+ header={tocTitle}
54
+ options={tocOptions}
55
+ value={tocValue}
56
+ ></dx-toc>
57
+ </div>
58
+ </div>
59
+ <div lwc:if={showFooter} class="footer-container">
60
+ <dx-footer variant="no-signup"></dx-footer>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </template>
@@ -0,0 +1,168 @@
1
+ import ContentLayout from "doc/contentLayout";
2
+
3
+ const TOC_HEADER_TAG = "doc-heading";
4
+ const RNB_BY_TAB = "docs-tab";
5
+ const SPECIFICATION_TAB_TITLE = "Specification";
6
+ export const OBSERVER_ATTACH_WAIT_TIME = 500;
7
+
8
+ export default class LwcContentLayout extends ContentLayout {
9
+ private rnbByTab: boolean = false;
10
+
11
+ private setRNBByTab() {
12
+ const tabPanelListItem: any = this.getTabPanelList();
13
+ this.rnbByTab = tabPanelListItem?.id === RNB_BY_TAB ? true : false;
14
+ }
15
+
16
+ get showTabBasedRNB() {
17
+ return this.rnbByTab;
18
+ }
19
+
20
+ onRNBClick = (event: CustomEvent) => {
21
+ event.stopPropagation();
22
+ const currentTab = this.getSelectedTabId();
23
+ if (currentTab === SPECIFICATION_TAB_TITLE) {
24
+ this.didScrollToSelectedHash = false;
25
+ }
26
+ };
27
+
28
+ onTabChanged = () => {
29
+ this.updateRNB();
30
+ };
31
+
32
+ private getTabPanelList() {
33
+ // eslint-disable-next-line @lwc/lwc/no-document-query
34
+ return document.querySelector("dx-tab-panel-list");
35
+ }
36
+
37
+ protected getHeadingElements() {
38
+ let headingElements = super.getHeadingElements();
39
+ if (this.showTabBasedRNB) {
40
+ const tabPanelListItem: any = this.getTabPanelList();
41
+ const tabPanels =
42
+ tabPanelListItem?.querySelectorAll("dx-tab-panel");
43
+ for (const tabPanelItem of tabPanels) {
44
+ if (tabPanelItem.active) {
45
+ // This is needed for Specification tab content
46
+ const specificationElement = tabPanelItem.querySelector(
47
+ "doc-specification-content"
48
+ );
49
+ if (specificationElement) {
50
+ headingElements =
51
+ specificationElement.shadowRoot.querySelectorAll(
52
+ TOC_HEADER_TAG
53
+ );
54
+ } else {
55
+ headingElements =
56
+ tabPanelItem.querySelectorAll(TOC_HEADER_TAG);
57
+ }
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ return headingElements;
63
+ }
64
+
65
+ private updateURL() {
66
+ const tabs = this.getAllTabs();
67
+ const selectedTabId = this.getSelectedTabId();
68
+ tabs.forEach((tab: any) => {
69
+ if (tab.getAttribute("aria-selected") === "true") {
70
+ const tabID = tab.getAttribute("aria-label");
71
+ const url = new URL(window.location.href);
72
+ if (selectedTabId !== tabID) {
73
+ url.searchParams.set("type", tabID);
74
+ url.hash = "";
75
+ window.history.pushState({}, "", url.toString());
76
+ }
77
+ }
78
+ });
79
+ }
80
+
81
+ // This event gets triggered when navigating back/forward
82
+ handlePopState = (): void => {
83
+ if (this.showTabBasedRNB) {
84
+ this.restoreTabSelection();
85
+ }
86
+ };
87
+
88
+ connectedCallback(): void {
89
+ super.connectedCallback();
90
+ window.addEventListener("popstate", this.handlePopState);
91
+ }
92
+
93
+ private getSelectedTabId() {
94
+ const urlParams = new URLSearchParams(window.location.search);
95
+ const selectedTabId = urlParams.get("type");
96
+ return selectedTabId;
97
+ }
98
+
99
+ private restoreTabSelection() {
100
+ requestAnimationFrame(() => {
101
+ const selectedTabId = this.getSelectedTabId();
102
+ if (selectedTabId) {
103
+ this.selectTabById(selectedTabId);
104
+ }
105
+ });
106
+ }
107
+
108
+ private getAllTabs(): any[] {
109
+ const tabPanelListItem: any = this.getTabPanelList();
110
+ if (tabPanelListItem?.shadowRoot) {
111
+ return Array.from(
112
+ tabPanelListItem.shadowRoot.querySelectorAll(
113
+ "dx-tab-panel-item"
114
+ )
115
+ ).map((tabPanelItem: any) =>
116
+ tabPanelItem.shadowRoot.querySelector("button")
117
+ );
118
+ }
119
+ return [];
120
+ }
121
+
122
+ private selectTabById(tabId: string) {
123
+ const tabs = this.getAllTabs();
124
+ tabs.forEach((tab: any) => {
125
+ if (tab.getAttribute("aria-label") === tabId) {
126
+ tab.click();
127
+ }
128
+ });
129
+ }
130
+
131
+ postRenderedCallback(): void {
132
+ this.setRNBByTab();
133
+ if (this.showTabBasedRNB) {
134
+ window.addEventListener("tabchanged", this.onTabChanged);
135
+ window.addEventListener(
136
+ "specificationdatarendered",
137
+ this.onTabChanged
138
+ );
139
+ window.addEventListener("selectedcontent", (event) =>
140
+ this.onRNBClick(event as CustomEvent)
141
+ );
142
+ this.restoreTabSelection();
143
+ }
144
+ }
145
+
146
+ disconnectedCallback(): void {
147
+ super.disconnectedCallback();
148
+ if (this.showTabBasedRNB) {
149
+ window.removeEventListener("tabchanged", this.onTabChanged);
150
+ window.removeEventListener(
151
+ "specificationdatarendered",
152
+ this.onTabChanged
153
+ );
154
+ window.removeEventListener("selectedcontent", (event) =>
155
+ this.onRNBClick(event as CustomEvent)
156
+ );
157
+ window.removeEventListener("popstate", this.handlePopState);
158
+ }
159
+ }
160
+
161
+ updateHeadingForRNB(): void {
162
+ // We only need to update URL in case of /docs and ignore if tabs are used anywhere else in DSC
163
+ if (this.showTabBasedRNB) {
164
+ this.updateURL();
165
+ }
166
+ super.updateHeadingForRNB();
167
+ }
168
+ }
@@ -0,0 +1,31 @@
1
+ @import "dxHelpers/reset";
2
+ @import "dxHelpers/text";
3
+ @import "dxHelpers/table";
4
+
5
+ .code {
6
+ color: #181818;
7
+ font-family: Courier, var(--dx-g-font-mono);
8
+ font-size: var(--dx-g-text-sm);
9
+ line-height: 150%;
10
+ background-color: #f4f4f4;
11
+ }
12
+
13
+ table {
14
+ width: 100%;
15
+ }
16
+
17
+ .specification-properties table {
18
+ display: table;
19
+ }
20
+
21
+ .left-border {
22
+ border-left: 1px solid var(--dx-g-gray-90);
23
+ }
24
+
25
+ .icon-cell {
26
+ text-align: center;
27
+ }
28
+
29
+ .icon {
30
+ display: inline-block;
31
+ }
@@ -0,0 +1,164 @@
1
+ <template>
2
+ <div class="specification-properties">
3
+ <template lwc:if={hasAttributes}>
4
+ <doc-heading
5
+ header="Attributes"
6
+ hash="attributes"
7
+ aria-level="2"
8
+ id="attributes"
9
+ ></doc-heading>
10
+ <table>
11
+ <thead>
12
+ <tr>
13
+ <th>Name</th>
14
+ <th>Description</th>
15
+ <th>Type</th>
16
+ <th>Default</th>
17
+ <th>Required</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <template for:each={attributes} for:item="attribute">
22
+ <tr key={attribute.name}>
23
+ <td>
24
+ <span class="code">
25
+ {attribute.nameInKebabCase}
26
+ </span>
27
+ </td>
28
+ <td>{attribute.description}</td>
29
+ <td>{attribute.type}</td>
30
+ <td>{attribute.default}</td>
31
+ <td class="icon-cell">
32
+ <template lwc:if={attribute.required}>
33
+ <dx-icon
34
+ symbol="success"
35
+ size="large"
36
+ color="green-vibrant-65"
37
+ class="icon"
38
+ ></dx-icon>
39
+ </template>
40
+ </td>
41
+ </tr>
42
+ </template>
43
+ </tbody>
44
+ </table>
45
+ </template>
46
+
47
+ <template lwc:if={hasMethods}>
48
+ <doc-heading
49
+ header="Methods"
50
+ hash="methods"
51
+ aria-level="2"
52
+ id="methods"
53
+ ></doc-heading>
54
+ <table>
55
+ <thead>
56
+ <tr>
57
+ <th>Name</th>
58
+ <th>Description</th>
59
+ <th>Argument Name</th>
60
+ <th>Argument Type</th>
61
+ <th>Argument Description</th>
62
+ </tr>
63
+ </thead>
64
+ <tbody>
65
+ <template for:each={processedMethods} for:item="method">
66
+ <template lwc:if={method.firstArgument}>
67
+ <tr key={method.name}>
68
+ <td rowspan={method.arguments.length}>
69
+ <span class="code">
70
+ {method.nameInKebabCase}
71
+ </span>
72
+ </td>
73
+ <td rowspan={method.arguments.length}>
74
+ {method.description}
75
+ </td>
76
+ <td class={method.cssForMultipleArguments}>
77
+ {method.firstArgument.name}
78
+ </td>
79
+ <td>{method.firstArgument.type}</td>
80
+ <td>{method.firstArgument.description}</td>
81
+ </tr>
82
+ <template
83
+ for:each={method.remainingArguments}
84
+ for:item="arg"
85
+ >
86
+ <tr key={arg.name}>
87
+ <td>{arg.name}</td>
88
+ <td>{arg.type}</td>
89
+ <td>{arg.description}</td>
90
+ </tr>
91
+ </template>
92
+ </template>
93
+ <template lwc:else>
94
+ <tr key={method.name}>
95
+ <td>
96
+ <span class="code">
97
+ {method.nameInKebabCase}
98
+ </span>
99
+ </td>
100
+ <td>{method.description}</td>
101
+ <td colspan="3"></td>
102
+ </tr>
103
+ </template>
104
+ </template>
105
+ </tbody>
106
+ </table>
107
+ </template>
108
+
109
+ <template lwc:if={hasSlots}>
110
+ <doc-heading
111
+ header="Slots"
112
+ hash="slots"
113
+ aria-level="2"
114
+ id="slots"
115
+ ></doc-heading>
116
+ <table>
117
+ <thead>
118
+ <tr>
119
+ <th>Name</th>
120
+ <th>Description</th>
121
+ </tr>
122
+ </thead>
123
+ <tbody>
124
+ <template for:each={slots} for:item="slot">
125
+ <tr key={slot.name}>
126
+ <td>
127
+ <span class="code">{slot.nameInKebabCase}</span>
128
+ </td>
129
+ <td>{slot.description}</td>
130
+ </tr>
131
+ </template>
132
+ </tbody>
133
+ </table>
134
+ </template>
135
+
136
+ <template lwc:if={hasEvents}>
137
+ <doc-heading
138
+ header="Events"
139
+ hash="events"
140
+ aria-level="2"
141
+ ></doc-heading>
142
+ <table>
143
+ <thead>
144
+ <tr>
145
+ <th>Name</th>
146
+ <th>Description</th>
147
+ </tr>
148
+ </thead>
149
+ <tbody>
150
+ <template for:each={events} for:item="event">
151
+ <tr key={event.name}>
152
+ <td>
153
+ <span class="code">
154
+ {event.nameInKebabCase}
155
+ </span>
156
+ </td>
157
+ <td>{event.description}</td>
158
+ </tr>
159
+ </template>
160
+ </tbody>
161
+ </table>
162
+ </template>
163
+ </div>
164
+ </template>
@@ -0,0 +1,114 @@
1
+ import { LightningElement, track, api } from "lwc";
2
+ import { Method, Specification } from "typings/custom";
3
+ import debounce from "debounce";
4
+
5
+ export default class SpecificationContent extends LightningElement {
6
+ @track data: any;
7
+ // TODO: added these default values for testing, will drop this once the backend is ready.
8
+ @api component: string = "button";
9
+ @api model: string = "lwc";
10
+ @api namespace: string = "lightning";
11
+
12
+ /* TODO: The actual URL is as follows:
13
+ * http://api.salesforce.com/doc-platform/developer/v1/{type}/{sub-type}/{component-name}
14
+ * Until the API integration is ready, we will go ahead with mocked-router-url.
15
+ */
16
+ @api apiBaseUrl: string =
17
+ "https://cx-mock-router-internal-07a18d7b3f61.herokuapp.com";
18
+
19
+ private attributes: Specification[] = [];
20
+ private methods: Method[] = [];
21
+ private slots: Specification[] = [];
22
+ private events: Specification[] = [];
23
+
24
+ /* TODO: For now setting the timeout to 300ms,
25
+ * post integration with CX-Router API will test and change if required.
26
+ */
27
+ private debouncedNotifyDataRendered = debounce(() => {
28
+ this.notifySpecificationDataRendered();
29
+ }, 300);
30
+
31
+ connectedCallback() {
32
+ this.fetchComponentMetadata();
33
+ }
34
+
35
+ async fetchComponentMetadata() {
36
+ const url = `${this.apiBaseUrl}/${this.model}/${this.namespace}/${this.component}`;
37
+
38
+ try {
39
+ const response = await fetch(url);
40
+
41
+ if (!response.ok) {
42
+ // TODO: Will add loader and show error as follow-up
43
+ throw new Error(`Failed to fetch: ${response.statusText}`);
44
+ }
45
+
46
+ const result = await response.json();
47
+ this.data = result;
48
+ ({
49
+ attributes: this.attributes,
50
+ methods: this.methods,
51
+ slots: this.slots,
52
+ events: this.events
53
+ } = this.data);
54
+ } catch (error) {
55
+ this.data = {};
56
+ console.error("fetchComponentMetadata() failed for:" + url);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * This getter is to preprocess the methods for easier rendering in the template.
62
+ * Each method is augmented with additional properties:
63
+ * - `firstArgument`: The first argument (if any).
64
+ * - `remainingArguments`: All other arguments (if any).
65
+ * - `hasArguments`: A boolean indicating whether the method has arguments or not.
66
+ */
67
+ get processedMethods(): Method[] {
68
+ return this.methods.map((method) => {
69
+ const [firstArgument, ...remainingArguments] =
70
+ method.arguments || [];
71
+ return {
72
+ ...method,
73
+ firstArgument,
74
+ remainingArguments,
75
+ hasArguments: method.arguments && method.arguments.length > 0,
76
+ cssForMultipleArguments:
77
+ remainingArguments.length > 0 ? "left-border" : ""
78
+ };
79
+ });
80
+ }
81
+
82
+ get hasAttributes() {
83
+ return this.attributes?.length > 0;
84
+ }
85
+
86
+ get hasMethods() {
87
+ return this.methods?.length > 0;
88
+ }
89
+
90
+ get hasSlots() {
91
+ return this.slots?.length > 0;
92
+ }
93
+
94
+ get hasEvents() {
95
+ return this.events?.length > 0;
96
+ }
97
+
98
+ renderedCallback(): void {
99
+ if (this.data) {
100
+ this.debouncedNotifyDataRendered();
101
+ }
102
+ }
103
+
104
+ notifySpecificationDataRendered() {
105
+ // Dispatch a custom event to notify the specification tab has rendered.
106
+ this.dispatchEvent(
107
+ new CustomEvent("specificationdatarendered", {
108
+ detail: { name: "doc-specification-content" },
109
+ bubbles: true,
110
+ composed: true
111
+ })
112
+ );
113
+ }
114
+ }
@@ -0,0 +1,131 @@
1
+ :host {
2
+ --dx-c-content-vertical-spacing: var(--dx-g-spacing-lg);
3
+ --dx-c-content-sidebar-sticky-top: calc(
4
+ var(--dx-g-global-header-height) + var(--dx-g-doc-header-height)
5
+ );
6
+ --dx-c-sidebar-height: calc(
7
+ 100vh -
8
+ calc(
9
+ var(--dx-g-global-header-height) + var(--dx-g-doc-header-height)
10
+ )
11
+ );
12
+ --dx-c-content-scroll-margin-top: calc(
13
+ var(--dx-g-global-header-height) + var(--dx-g-doc-header-height) +
14
+ var(--dx-g-spacing-2xl)
15
+ );
16
+
17
+ display: block;
18
+ }
19
+
20
+ doc-breadcrumbs {
21
+ --dx-c-popover-z-index: 5;
22
+
23
+ display: block;
24
+ margin-bottom: var(--dx-g-spacing-2xl);
25
+ }
26
+
27
+ dx-sidebar,
28
+ dx-sidebar-old {
29
+ --dx-c-sidebar-vertical-padding: var(--dx-g-spacing-md);
30
+
31
+ z-index: calc(var(--dx-g-z-index-100) + 5);
32
+ }
33
+
34
+ dx-toc {
35
+ --dx-c-toc-width: unset;
36
+
37
+ height: calc(100% - var(--dx-c-content-vertical-spacing) * 2);
38
+ margin: var(--dx-c-content-vertical-spacing) 0;
39
+ overflow-y: auto;
40
+ }
41
+
42
+ dx-sidebar,
43
+ dx-toc {
44
+ display: block;
45
+ }
46
+
47
+ /* offset page jump link due to fixed header */
48
+ ::slotted(doc-heading) {
49
+ scroll-margin-top: var(--dx-c-content-scroll-margin-top);
50
+ }
51
+
52
+ .content {
53
+ display: flex;
54
+ position: relative;
55
+ }
56
+
57
+ .content-body-doc-phase-container {
58
+ flex: 1;
59
+ border-left: 1px solid var(--dx-g-gray-90);
60
+ }
61
+
62
+ .content-body-container {
63
+ display: flex;
64
+ flex-direction: row;
65
+ justify-content: center;
66
+ max-width: var(--dx-g-doc-content-max-width);
67
+
68
+ /* Derived this manually by substracting (topHeader, doc header, banner and the content). */
69
+ min-height: 62vh;
70
+ margin: auto;
71
+ padding: 0 var(--dx-g-global-header-padding-horizontal);
72
+ margin-bottom: calc(2 * (var(--dx-g-spacing-5xl) + 4px));
73
+ }
74
+
75
+ .content-body {
76
+ margin: var(--dx-g-spacing-md) 0 0;
77
+ max-width: 900px;
78
+ flex: 1;
79
+ width: 0;
80
+ }
81
+
82
+ .is-sticky {
83
+ align-self: flex-start;
84
+ position: sticky;
85
+ top: var(--dx-c-content-sidebar-sticky-top);
86
+ }
87
+
88
+ .right-nav-bar {
89
+ margin-left: var(--dx-g-spacing-2xl);
90
+ }
91
+
92
+ @media screen and (max-width: 1024px) {
93
+ .right-nav-bar {
94
+ display: none;
95
+ }
96
+ }
97
+
98
+ @media screen and (max-width: 800px) {
99
+ .content-body {
100
+ margin-top: var(--dx-c-content-vertical-spacing);
101
+ }
102
+
103
+ .content-body-doc-phase-container {
104
+ border-left: 0;
105
+ }
106
+ }
107
+
108
+ @media screen and (max-width: 768px) {
109
+ .is-sticky {
110
+ width: 100%;
111
+ }
112
+
113
+ .content {
114
+ flex-direction: column;
115
+ }
116
+
117
+ .content-body-container {
118
+ padding-right: 0;
119
+ overflow-x: auto;
120
+ margin-bottom: calc(var(--dx-g-spacing-5xl) + 4px);
121
+ }
122
+
123
+ .left-nav-bar {
124
+ height: unset;
125
+ }
126
+
127
+ .content-body {
128
+ margin-left: var(--dx-g-spacing-mlg, 20px);
129
+ margin-right: var(--dx-g-spacing-mlg, 20px);
130
+ }
131
+ }