@salesforcedevs/docs-components 1.3.344 → 1.3.345-refactor-tab-alpha
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 +2 -0
- package/package.json +2 -2
- package/src/modules/doc/contentLayout/contentLayout.css +1 -131
- package/src/modules/doc/contentLayout/contentLayout.ts +64 -171
- package/src/modules/doc/lwcContentLayout/lwcContentLayout.css +1 -0
- package/src/modules/doc/lwcContentLayout/lwcContentLayout.html +64 -0
- package/src/modules/doc/lwcContentLayout/lwcContentLayout.ts +277 -0
- package/src/modules/doc/specificationContent/specificationContent.css +3 -0
- package/src/modules/doc/specificationContent/specificationContent.html +145 -0
- package/src/modules/doc/specificationContent/specificationContent.ts +112 -0
- package/src/modules/docHelpers/contentLayoutStyle/contentLayoutStyle.css +131 -0
- package/LICENSE +0 -12
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.
|
|
3
|
+
"version": "1.3.345-refactor-tab-alpha",
|
|
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": "
|
|
27
|
+
"gitHead": "4629fdd9ca18a13480044ad43515b91945d16aad"
|
|
28
28
|
}
|
|
@@ -1,131 +1 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
+
protected _sidebarContent: unknown;
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
protected _breadcrumbs = null;
|
|
89
89
|
|
|
90
90
|
@track
|
|
91
|
-
|
|
91
|
+
protected _tocOptions!: Array<unknown>;
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
+
protected tocValue?: string = undefined;
|
|
181
120
|
// eslint-disable-next-line no-undef
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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,45 +149,6 @@ 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
|
-
}
|
|
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
152
|
}
|
|
260
153
|
|
|
261
154
|
renderedCallback(): void {
|
|
@@ -275,11 +168,6 @@ export default class ContentLayout extends LightningElement {
|
|
|
275
168
|
|
|
276
169
|
if (!this.hasRendered) {
|
|
277
170
|
this.hasRendered = true;
|
|
278
|
-
this.setRNBByTab();
|
|
279
|
-
if (this.showTabBasedRNB) {
|
|
280
|
-
window.addEventListener("tabchanged", this.onTabChanged);
|
|
281
|
-
this.restoreTabSelection();
|
|
282
|
-
}
|
|
283
171
|
this.restoreScroll();
|
|
284
172
|
}
|
|
285
173
|
}
|
|
@@ -292,8 +180,6 @@ export default class ContentLayout extends LightningElement {
|
|
|
292
180
|
);
|
|
293
181
|
window.removeEventListener("scroll", this.adjustNavPosition);
|
|
294
182
|
window.removeEventListener("resize", this.adjustNavPosition);
|
|
295
|
-
window.removeEventListener("tabchanged", this.onTabChanged);
|
|
296
|
-
window.removeEventListener("popstate", this.handlePopState);
|
|
297
183
|
this.searchSyncer.dispose();
|
|
298
184
|
this.clearRenderObserverTimer();
|
|
299
185
|
|
|
@@ -439,12 +325,7 @@ export default class ContentLayout extends LightningElement {
|
|
|
439
325
|
);
|
|
440
326
|
|
|
441
327
|
// Note: We are doing document.querySelectorAll as a quick fix as we are not getting heading elements reference this.querySelectorAll
|
|
442
|
-
const headingElements =
|
|
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
|
-
}
|
|
328
|
+
const headingElements = document.querySelectorAll(TOC_HEADER_TAG);
|
|
448
329
|
|
|
449
330
|
for (const headingElement of headingElements as any) {
|
|
450
331
|
// Add headingElements to intersectionObserver for highlighting respective RNB item when user scroll
|
|
@@ -456,45 +337,57 @@ export default class ContentLayout extends LightningElement {
|
|
|
456
337
|
this.observer.observe(headingElement);
|
|
457
338
|
}
|
|
458
339
|
|
|
459
|
-
this.contentLoaded = true;
|
|
460
|
-
|
|
461
340
|
if (!this.didScrollToSelectedHash) {
|
|
462
341
|
this.didScrollToSelectedHash = true;
|
|
463
342
|
this.scrollToHash(headingElements);
|
|
464
343
|
}
|
|
465
344
|
};
|
|
466
345
|
|
|
467
|
-
onSlotChange(): void {
|
|
468
|
-
|
|
469
|
-
|
|
346
|
+
onSlotChange(event: Event): void {
|
|
347
|
+
const slotElements = (
|
|
348
|
+
event.target as HTMLSlotElement
|
|
349
|
+
).assignedElements();
|
|
350
|
+
|
|
351
|
+
if (slotElements.length) {
|
|
352
|
+
this.contentLoaded = true;
|
|
353
|
+
const slotContentElement = slotElements[0];
|
|
354
|
+
const headingElements =
|
|
355
|
+
slotContentElement.ownerDocument?.getElementsByTagName(
|
|
356
|
+
TOC_HEADER_TAG
|
|
357
|
+
);
|
|
470
358
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
359
|
+
for (const headingElement of headingElements as any) {
|
|
360
|
+
// Sometimes elements hash and header is not being set when slot content is wrapped with div
|
|
361
|
+
headingElement.hash = headingElement.attributes.hash?.nodeValue;
|
|
362
|
+
headingElement.header =
|
|
363
|
+
headingElement.attributes.header?.nodeValue;
|
|
364
|
+
}
|
|
474
365
|
|
|
475
|
-
|
|
476
|
-
headingElement
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
366
|
+
const tocOptions = [];
|
|
367
|
+
for (const headingElement of headingElements as any) {
|
|
368
|
+
headingElement.id = headingElement.hash;
|
|
369
|
+
|
|
370
|
+
// Update tocOptions from anchorTags only for H2, consider default as 2 as per component
|
|
371
|
+
const headingAriaLevel =
|
|
372
|
+
headingElement.attributes["aria-level"]?.nodeValue || "2";
|
|
373
|
+
const isH2 = headingAriaLevel === "2";
|
|
374
|
+
|
|
375
|
+
if (isH2) {
|
|
376
|
+
const tocItem = {
|
|
377
|
+
anchor: `#${headingElement.hash}`,
|
|
378
|
+
id: headingElement.id,
|
|
379
|
+
label: headingElement.header
|
|
380
|
+
};
|
|
381
|
+
tocOptions.push(tocItem);
|
|
382
|
+
this.tocOptionIdsSet.add(headingElement.id);
|
|
383
|
+
}
|
|
491
384
|
}
|
|
492
|
-
}
|
|
493
385
|
|
|
494
|
-
|
|
386
|
+
this._tocOptions = tocOptions;
|
|
387
|
+
}
|
|
495
388
|
}
|
|
496
389
|
|
|
497
|
-
|
|
390
|
+
protected disconnectObserver(): void {
|
|
498
391
|
if (this.observer) {
|
|
499
392
|
this.observer.disconnect();
|
|
500
393
|
this.observer = undefined;
|
|
@@ -502,7 +395,7 @@ export default class ContentLayout extends LightningElement {
|
|
|
502
395
|
}
|
|
503
396
|
|
|
504
397
|
// eslint-disable-next-line no-undef
|
|
505
|
-
|
|
398
|
+
protected scrollToHash(headingElements: NodeListOf<Element>): void {
|
|
506
399
|
let { hash } = window.location;
|
|
507
400
|
if (hash) {
|
|
508
401
|
hash = hash.substr(1);
|
|
@@ -539,7 +432,7 @@ export default class ContentLayout extends LightningElement {
|
|
|
539
432
|
}
|
|
540
433
|
}
|
|
541
434
|
|
|
542
|
-
|
|
435
|
+
protected scrollIntoViewWithOffset(
|
|
543
436
|
headingElement: HTMLElement,
|
|
544
437
|
offset: number
|
|
545
438
|
) {
|
|
@@ -552,7 +445,7 @@ export default class ContentLayout extends LightningElement {
|
|
|
552
445
|
});
|
|
553
446
|
}
|
|
554
447
|
|
|
555
|
-
|
|
448
|
+
protected calculateActualSection(): void {
|
|
556
449
|
const currentScrollPosition = document.documentElement.scrollTop;
|
|
557
450
|
const id = Object.keys(this.anchoredElements).find(
|
|
558
451
|
(_id) => this.anchoredElements[_id].intersect
|
|
@@ -567,21 +460,21 @@ export default class ContentLayout extends LightningElement {
|
|
|
567
460
|
this.lastScrollPosition = currentScrollPosition;
|
|
568
461
|
}
|
|
569
462
|
|
|
570
|
-
|
|
463
|
+
protected calculatePreviousElementId(): string | undefined {
|
|
571
464
|
const keys = Object.keys(this.anchoredElements);
|
|
572
465
|
const currentIndex = keys.findIndex((id) => this.tocValue === id);
|
|
573
466
|
|
|
574
467
|
return currentIndex > 0 ? keys[currentIndex - 1] : undefined;
|
|
575
468
|
}
|
|
576
469
|
|
|
577
|
-
|
|
470
|
+
protected assignElementId(id: string | undefined): void {
|
|
578
471
|
// Change toc(RNB) highlight only for H2
|
|
579
472
|
if (this.tocOptionIdsSet.has(id)) {
|
|
580
473
|
this.tocValue = id;
|
|
581
474
|
}
|
|
582
475
|
}
|
|
583
476
|
|
|
584
|
-
|
|
477
|
+
protected dispatchHighlightChange(term: string): void {
|
|
585
478
|
this.dispatchEvent(
|
|
586
479
|
new CustomEvent("highlightedtermchange", {
|
|
587
480
|
detail: term,
|
|
@@ -591,14 +484,14 @@ export default class ContentLayout extends LightningElement {
|
|
|
591
484
|
);
|
|
592
485
|
}
|
|
593
486
|
|
|
594
|
-
|
|
487
|
+
protected updateHighlightsAndSearch(nextSearchString: string): void {
|
|
595
488
|
const nextSearchParam =
|
|
596
489
|
new URLSearchParams(nextSearchString).get("q") || "";
|
|
597
490
|
this.setSidebarInputValue(nextSearchParam);
|
|
598
491
|
this.dispatchHighlightChange(nextSearchParam);
|
|
599
492
|
}
|
|
600
493
|
|
|
601
|
-
|
|
494
|
+
protected onToggleSidebar(e: CustomEvent): void {
|
|
602
495
|
this.sidebarOpen = e.detail.open;
|
|
603
496
|
|
|
604
497
|
// 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,277 @@
|
|
|
1
|
+
/* eslint-disable @lwc/lwc/no-document-query */
|
|
2
|
+
import { closest } from "kagekiri";
|
|
3
|
+
import ContentLayout from "doc/contentLayout";
|
|
4
|
+
|
|
5
|
+
const TOC_HEADER_TAG = "doc-heading";
|
|
6
|
+
const RNB_BY_TAB = "docs-tab";
|
|
7
|
+
const SPECIFICATION_TAB_TITLE = "Specification";
|
|
8
|
+
export const OBSERVER_ATTACH_WAIT_TIME = 500;
|
|
9
|
+
|
|
10
|
+
export default class LwcContentLayout extends ContentLayout {
|
|
11
|
+
private rnbByTab: boolean = false;
|
|
12
|
+
|
|
13
|
+
private setRNBByTab() {
|
|
14
|
+
const tabPanelListItem: any = this.getTabPanelList();
|
|
15
|
+
this.rnbByTab = tabPanelListItem?.id === RNB_BY_TAB ? true : false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get showTabBasedRNB() {
|
|
19
|
+
return this.rnbByTab;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onRNBClick = (event: CustomEvent) => {
|
|
23
|
+
event.stopPropagation();
|
|
24
|
+
const currentTab = this.getSelectedTabId();
|
|
25
|
+
if (currentTab === SPECIFICATION_TAB_TITLE) {
|
|
26
|
+
this.didScrollToSelectedHash = false;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
onTabChanged = () => {
|
|
31
|
+
this.updateRNB();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
private getTabPanelList() {
|
|
35
|
+
return document.querySelector("dx-tab-panel-list");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
updateRNB = () => {
|
|
39
|
+
const headingElements = this.getHeadingElements();
|
|
40
|
+
headingElements.forEach((headingElement: any) => {
|
|
41
|
+
// Sometimes elements hash and header is not being set when slot content is wrapped with div
|
|
42
|
+
if (!headingElement.hash) {
|
|
43
|
+
headingElement.hash = headingElement.attributes.hash?.nodeValue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!headingElement.header) {
|
|
47
|
+
headingElement.header =
|
|
48
|
+
headingElement.attributes.header?.nodeValue;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
this.updateTocItems(headingElements);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
private getHeadingElements() {
|
|
55
|
+
let headingElements = document.querySelectorAll(TOC_HEADER_TAG);
|
|
56
|
+
if (this.showTabBasedRNB) {
|
|
57
|
+
const tabPanelListItem: any = this.getTabPanelList();
|
|
58
|
+
const tabPanels =
|
|
59
|
+
tabPanelListItem?.querySelectorAll("dx-tab-panel");
|
|
60
|
+
for (const tabPanelItem of tabPanels) {
|
|
61
|
+
if (tabPanelItem.active) {
|
|
62
|
+
// This is needed for Specification tab content
|
|
63
|
+
const specificationElement = tabPanelItem.querySelector(
|
|
64
|
+
"doc-specification-content"
|
|
65
|
+
);
|
|
66
|
+
if (specificationElement) {
|
|
67
|
+
headingElements =
|
|
68
|
+
specificationElement.shadowRoot.querySelectorAll(
|
|
69
|
+
TOC_HEADER_TAG
|
|
70
|
+
);
|
|
71
|
+
} else {
|
|
72
|
+
headingElements =
|
|
73
|
+
tabPanelItem.querySelectorAll(TOC_HEADER_TAG);
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return headingElements;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private updateURL() {
|
|
83
|
+
const tabs = this.getAllTabs();
|
|
84
|
+
const selectedTabId = this.getSelectedTabId();
|
|
85
|
+
tabs.forEach((tab: any) => {
|
|
86
|
+
if (tab.getAttribute("aria-selected") === "true") {
|
|
87
|
+
const tabID = tab.getAttribute("aria-label");
|
|
88
|
+
const url = new URL(window.location.href);
|
|
89
|
+
if (selectedTabId !== tabID) {
|
|
90
|
+
url.searchParams.set("type", tabID);
|
|
91
|
+
url.hash = "";
|
|
92
|
+
window.history.pushState({}, "", url.toString());
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// This event gets triggered when navigating back/forward
|
|
99
|
+
handlePopState = (): void => {
|
|
100
|
+
if (this.showTabBasedRNB) {
|
|
101
|
+
this.restoreTabSelection();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
connectedCallback(): void {
|
|
106
|
+
const hasParentHighlightListener = closest(
|
|
107
|
+
"doc-xml-content",
|
|
108
|
+
this.template.host
|
|
109
|
+
);
|
|
110
|
+
if (!hasParentHighlightListener) {
|
|
111
|
+
window.addEventListener(
|
|
112
|
+
"highlightedtermchange",
|
|
113
|
+
this.updateHighlighted
|
|
114
|
+
);
|
|
115
|
+
this.searchSyncer.init();
|
|
116
|
+
}
|
|
117
|
+
window.addEventListener("popstate", this.handlePopState);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private getSelectedTabId() {
|
|
121
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
122
|
+
const selectedTabId = urlParams.get("type");
|
|
123
|
+
return selectedTabId;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private restoreTabSelection() {
|
|
127
|
+
requestAnimationFrame(() => {
|
|
128
|
+
const selectedTabId = this.getSelectedTabId();
|
|
129
|
+
if (selectedTabId) {
|
|
130
|
+
this.selectTabById(selectedTabId);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private getAllTabs(): any[] {
|
|
136
|
+
const tabPanelListItem: any = this.getTabPanelList();
|
|
137
|
+
if (tabPanelListItem?.shadowRoot) {
|
|
138
|
+
return Array.from(
|
|
139
|
+
tabPanelListItem.shadowRoot.querySelectorAll(
|
|
140
|
+
"dx-tab-panel-item"
|
|
141
|
+
)
|
|
142
|
+
).map((tabPanelItem: any) =>
|
|
143
|
+
tabPanelItem.shadowRoot.querySelector("button")
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private selectTabById(tabId: string) {
|
|
150
|
+
const tabs = this.getAllTabs();
|
|
151
|
+
tabs.forEach((tab: any) => {
|
|
152
|
+
if (tab.getAttribute("aria-label") === tabId) {
|
|
153
|
+
tab.click();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
renderedCallback(): void {
|
|
159
|
+
/**
|
|
160
|
+
* Note: We are adding timeout because chrome is optimizing and not triggering recent renderedCallback though elements reference is changed
|
|
161
|
+
* Also we are considering recent renderedCallback
|
|
162
|
+
*/
|
|
163
|
+
this.clearRenderObserverTimer();
|
|
164
|
+
this.observerTimerId = setTimeout(
|
|
165
|
+
this.attachInteractionObserver,
|
|
166
|
+
OBSERVER_ATTACH_WAIT_TIME
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
this.adjustNavPosition();
|
|
170
|
+
window.addEventListener("scroll", this.adjustNavPosition);
|
|
171
|
+
window.addEventListener("resize", this.adjustNavPosition);
|
|
172
|
+
|
|
173
|
+
if (!this.hasRendered) {
|
|
174
|
+
this.hasRendered = true;
|
|
175
|
+
this.setRNBByTab();
|
|
176
|
+
if (this.showTabBasedRNB) {
|
|
177
|
+
window.addEventListener("tabchanged", this.onTabChanged);
|
|
178
|
+
this.restoreTabSelection();
|
|
179
|
+
}
|
|
180
|
+
this.restoreScroll();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
disconnectedCallback(): void {
|
|
185
|
+
if (this.showTabBasedRNB) {
|
|
186
|
+
window.removeEventListener("tabchanged", this.onTabChanged);
|
|
187
|
+
window.removeEventListener(
|
|
188
|
+
"specificationdatarendered",
|
|
189
|
+
this.onTabChanged
|
|
190
|
+
);
|
|
191
|
+
window.removeEventListener("selectedcontent", (event) =>
|
|
192
|
+
this.onRNBClick(event as CustomEvent)
|
|
193
|
+
);
|
|
194
|
+
window.removeEventListener("popstate", this.handlePopState);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
attachInteractionObserver = (): void => {
|
|
199
|
+
if (!this.enableSlotChange) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
this.disconnectObserver();
|
|
203
|
+
|
|
204
|
+
const globalNavOffset = `-${getComputedStyle(
|
|
205
|
+
document.documentElement
|
|
206
|
+
).getPropertyValue("--dx-g-doc-header-main-nav-height")}`;
|
|
207
|
+
|
|
208
|
+
this.observer = new IntersectionObserver(
|
|
209
|
+
(entries) => {
|
|
210
|
+
entries.forEach(
|
|
211
|
+
(entry) =>
|
|
212
|
+
(this.anchoredElements[
|
|
213
|
+
entry.target.getAttribute("id")!
|
|
214
|
+
].intersect = entry.isIntersecting)
|
|
215
|
+
);
|
|
216
|
+
this.calculateActualSection();
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
rootMargin: globalNavOffset.trim()
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Note: We are doing document.querySelectorAll as a quick fix as we are not getting heading elements reference this.querySelectorAll
|
|
224
|
+
const headingElements = this.getHeadingElements();
|
|
225
|
+
|
|
226
|
+
// We only need to update URL in case of /docs and ignore if tabs are used anywhere else in DSC
|
|
227
|
+
if (this.showTabBasedRNB) {
|
|
228
|
+
this.updateURL();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const headingElement of headingElements as any) {
|
|
232
|
+
// Add headingElements to intersectionObserver for highlighting respective RNB item when user scroll
|
|
233
|
+
const id = headingElement.getAttribute("id")!;
|
|
234
|
+
this.anchoredElements[id] = {
|
|
235
|
+
id,
|
|
236
|
+
intersect: false
|
|
237
|
+
};
|
|
238
|
+
this.observer.observe(headingElement);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!this.didScrollToSelectedHash) {
|
|
242
|
+
this.didScrollToSelectedHash = true;
|
|
243
|
+
this.scrollToHash(headingElements);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
onSlotChange(): void {
|
|
248
|
+
this.updateRNB();
|
|
249
|
+
this.contentLoaded = true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// eslint-disable-next-line no-undef
|
|
253
|
+
private updateTocItems(headingElements: NodeListOf<Element>): void {
|
|
254
|
+
const tocOptions = [];
|
|
255
|
+
|
|
256
|
+
for (const headingElement of headingElements as any) {
|
|
257
|
+
headingElement.id = headingElement.hash;
|
|
258
|
+
|
|
259
|
+
// Update tocOptions from anchorTags only for H2, consider default as 2 as per component
|
|
260
|
+
const headingAriaLevel =
|
|
261
|
+
headingElement.attributes["aria-level"]?.nodeValue || "2";
|
|
262
|
+
const isH2 = headingAriaLevel === "2";
|
|
263
|
+
|
|
264
|
+
if (isH2) {
|
|
265
|
+
const tocItem = {
|
|
266
|
+
anchor: `#${headingElement.hash}`,
|
|
267
|
+
id: headingElement.id,
|
|
268
|
+
label: headingElement.header
|
|
269
|
+
};
|
|
270
|
+
tocOptions.push(tocItem);
|
|
271
|
+
this.tocOptionIdsSet.add(headingElement.id);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
this._tocOptions = tocOptions;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="specification-properties">
|
|
3
|
+
<template if:true={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>{attribute.nameInKebabCase}</td>
|
|
24
|
+
<td>{attribute.description}</td>
|
|
25
|
+
<td>{attribute.type}</td>
|
|
26
|
+
<td>{attribute.default}</td>
|
|
27
|
+
<td>
|
|
28
|
+
<template lwc:if={attribute.required}>
|
|
29
|
+
<dx-icon
|
|
30
|
+
symbol="success"
|
|
31
|
+
size="large"
|
|
32
|
+
color="green-vibrant-65"
|
|
33
|
+
></dx-icon>
|
|
34
|
+
</template>
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
</template>
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<template if:true={hasMethods}>
|
|
43
|
+
<doc-heading
|
|
44
|
+
header="Methods"
|
|
45
|
+
hash="methods"
|
|
46
|
+
aria-level="2"
|
|
47
|
+
id="methods"
|
|
48
|
+
></doc-heading>
|
|
49
|
+
<table>
|
|
50
|
+
<thead>
|
|
51
|
+
<tr>
|
|
52
|
+
<th>Name</th>
|
|
53
|
+
<th>Description</th>
|
|
54
|
+
<th>Argument Name</th>
|
|
55
|
+
<th>Argument Type</th>
|
|
56
|
+
<th>Argument Description</th>
|
|
57
|
+
</tr>
|
|
58
|
+
</thead>
|
|
59
|
+
<tbody>
|
|
60
|
+
<template for:each={processedMethods} for:item="method">
|
|
61
|
+
<template if:true={method.firstArgument}>
|
|
62
|
+
<tr key={method.name}>
|
|
63
|
+
<td rowspan={method.arguments.length}>
|
|
64
|
+
{method.nameInKebabCase}
|
|
65
|
+
</td>
|
|
66
|
+
<td rowspan={method.arguments.length}>
|
|
67
|
+
{method.description}
|
|
68
|
+
</td>
|
|
69
|
+
<td>{method.firstArgument.name}</td>
|
|
70
|
+
<td>{method.firstArgument.type}</td>
|
|
71
|
+
<td>{method.firstArgument.description}</td>
|
|
72
|
+
</tr>
|
|
73
|
+
<template
|
|
74
|
+
for:each={method.remainingArguments}
|
|
75
|
+
for:item="arg"
|
|
76
|
+
>
|
|
77
|
+
<tr key={arg.name}>
|
|
78
|
+
<td>{arg.name}</td>
|
|
79
|
+
<td>{arg.type}</td>
|
|
80
|
+
<td>{arg.description}</td>
|
|
81
|
+
</tr>
|
|
82
|
+
</template>
|
|
83
|
+
</template>
|
|
84
|
+
<template if:false={method.firstArgument}>
|
|
85
|
+
<tr key={method.name}>
|
|
86
|
+
<td>{method.nameInKebabCase}</td>
|
|
87
|
+
<td>{method.description}</td>
|
|
88
|
+
<td colspan="3"></td>
|
|
89
|
+
</tr>
|
|
90
|
+
</template>
|
|
91
|
+
</template>
|
|
92
|
+
</tbody>
|
|
93
|
+
</table>
|
|
94
|
+
</template>
|
|
95
|
+
|
|
96
|
+
<template if:true={hasSlots}>
|
|
97
|
+
<doc-heading
|
|
98
|
+
header="Slots"
|
|
99
|
+
hash="slots"
|
|
100
|
+
aria-level="2"
|
|
101
|
+
id="slots"
|
|
102
|
+
></doc-heading>
|
|
103
|
+
<table>
|
|
104
|
+
<thead>
|
|
105
|
+
<tr>
|
|
106
|
+
<th>Name</th>
|
|
107
|
+
<th>Description</th>
|
|
108
|
+
</tr>
|
|
109
|
+
</thead>
|
|
110
|
+
<tbody>
|
|
111
|
+
<template for:each={slots} for:item="slot">
|
|
112
|
+
<tr key={slot.name}>
|
|
113
|
+
<td>{slot.nameInKebabCase}</td>
|
|
114
|
+
<td>{slot.description}</td>
|
|
115
|
+
</tr>
|
|
116
|
+
</template>
|
|
117
|
+
</tbody>
|
|
118
|
+
</table>
|
|
119
|
+
</template>
|
|
120
|
+
|
|
121
|
+
<template if:true={hasEvents}>
|
|
122
|
+
<doc-heading
|
|
123
|
+
header="Events"
|
|
124
|
+
hash="events"
|
|
125
|
+
aria-level="2"
|
|
126
|
+
></doc-heading>
|
|
127
|
+
<table>
|
|
128
|
+
<thead>
|
|
129
|
+
<tr>
|
|
130
|
+
<th>Name</th>
|
|
131
|
+
<th>Description</th>
|
|
132
|
+
</tr>
|
|
133
|
+
</thead>
|
|
134
|
+
<tbody>
|
|
135
|
+
<template for:each={events} for:item="event">
|
|
136
|
+
<tr key={event.name}>
|
|
137
|
+
<td>{event.nameInKebabCase}</td>
|
|
138
|
+
<td>{event.description}</td>
|
|
139
|
+
</tr>
|
|
140
|
+
</template>
|
|
141
|
+
</tbody>
|
|
142
|
+
</table>
|
|
143
|
+
</template>
|
|
144
|
+
</div>
|
|
145
|
+
</template>
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get hasAttributes() {
|
|
81
|
+
return this.attributes?.length > 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get hasMethods() {
|
|
85
|
+
return this.methods?.length > 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get hasSlots() {
|
|
89
|
+
return this.slots?.length > 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get hasEvents() {
|
|
93
|
+
return this.events?.length > 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
renderedCallback(): void {
|
|
97
|
+
if (this.data) {
|
|
98
|
+
this.debouncedNotifyDataRendered();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
notifySpecificationDataRendered() {
|
|
103
|
+
// Dispatch a custom event to notify the specification tab has rendered.
|
|
104
|
+
this.dispatchEvent(
|
|
105
|
+
new CustomEvent("specificationdatarendered", {
|
|
106
|
+
detail: { name: "doc-specification-content" },
|
|
107
|
+
bubbles: true,
|
|
108
|
+
composed: true
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -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
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
Copyright (c) 2020, Salesforce.com, Inc.
|
|
2
|
-
All rights reserved.
|
|
3
|
-
|
|
4
|
-
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
5
|
-
|
|
6
|
-
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
7
|
-
|
|
8
|
-
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
9
|
-
|
|
10
|
-
* Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
11
|
-
|
|
12
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|