@salesforcedevs/docs-components 1.34.4-final-alpha1 → 1.34.4-scroll-2
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/package.json +3 -2
- package/src/modules/doc/contentLayout/contentLayout.ts +26 -7
- package/src/modules/doc/redocReference/redocReference.css +0 -1
- package/src/modules/doc/redocReference/redocReference.ts +21 -297
- package/src/modules/doc/unifiedContentLayout/unifiedContentLayout.html +0 -8
- package/src/modules/doc/unifiedContentLayout/unifiedContentLayout.ts +46 -65
- package/src/modules/doc/versionPicker/versionPicker.css +17 -204
- package/src/modules/doc/versionPicker/versionPicker.html +21 -54
- package/src/modules/doc/versionPicker/versionPicker.ts +0 -92
- package/src/modules/docUtils/utils/utils.ts +2 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforcedevs/docs-components",
|
|
3
|
-
"version": "1.34.4-
|
|
3
|
+
"version": "1.34.4-scroll-2",
|
|
4
4
|
"description": "Docs Lightning web components for DSC",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "index.js",
|
|
@@ -25,5 +25,6 @@
|
|
|
25
25
|
"@types/lodash.orderby": "4.6.9",
|
|
26
26
|
"@types/lodash.uniqby": "4.7.9"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "4629fdd9ca18a13480044ad43515b91945d16aad"
|
|
28
|
+
"gitHead": "4629fdd9ca18a13480044ad43515b91945d16aad",
|
|
29
|
+
"stableVersion": "1.34.4"
|
|
29
30
|
}
|
|
@@ -496,8 +496,18 @@ export default class ContentLayout extends LightningElement {
|
|
|
496
496
|
}
|
|
497
497
|
|
|
498
498
|
if (!this.didScrollToSelectedHash) {
|
|
499
|
+
this.tryScrollToHash(headingElements);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Completes the one-shot if no hash exists or scrolling succeeds,
|
|
505
|
+
* retrying only when the target isn't available yet to prevent
|
|
506
|
+
* unexpected scrolls on observer re-attach. */
|
|
507
|
+
// eslint-disable-next-line no-undef
|
|
508
|
+
protected tryScrollToHash(headingElements: NodeListOf<Element>): void {
|
|
509
|
+
if (!window.location.hash || this.scrollToHash(headingElements)) {
|
|
499
510
|
this.didScrollToSelectedHash = true;
|
|
500
|
-
this.scrollToHash(headingElements);
|
|
501
511
|
}
|
|
502
512
|
}
|
|
503
513
|
|
|
@@ -585,6 +595,10 @@ export default class ContentLayout extends LightningElement {
|
|
|
585
595
|
this.updateRNB();
|
|
586
596
|
this.updateContentActionToolbar();
|
|
587
597
|
this.contentLoaded = true;
|
|
598
|
+
|
|
599
|
+
if (this.enableSlotChange && !this.didScrollToSelectedHash) {
|
|
600
|
+
this.tryScrollToHash(this.getHeadingElements());
|
|
601
|
+
}
|
|
588
602
|
}
|
|
589
603
|
|
|
590
604
|
protected disconnectObserver(): void {
|
|
@@ -595,7 +609,7 @@ export default class ContentLayout extends LightningElement {
|
|
|
595
609
|
}
|
|
596
610
|
|
|
597
611
|
// eslint-disable-next-line no-undef
|
|
598
|
-
protected scrollToHash(headingElements: NodeListOf<Element>):
|
|
612
|
+
protected scrollToHash(headingElements: NodeListOf<Element>): boolean {
|
|
599
613
|
let { hash } = window.location;
|
|
600
614
|
if (hash) {
|
|
601
615
|
hash = hash.substr(1);
|
|
@@ -610,14 +624,18 @@ export default class ContentLayout extends LightningElement {
|
|
|
610
624
|
"hgf-c360contextnav"
|
|
611
625
|
) as HTMLElement;
|
|
612
626
|
|
|
627
|
+
if (!docHeaderEl || !globalNavEl || !contextNavEl) {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
|
|
613
631
|
const headerHeight =
|
|
614
|
-
docHeaderEl
|
|
615
|
-
globalNavEl
|
|
616
|
-
contextNavEl
|
|
632
|
+
(docHeaderEl.offsetHeight || 0) +
|
|
633
|
+
(globalNavEl.offsetHeight || 0) +
|
|
634
|
+
(contextNavEl.offsetHeight || 0);
|
|
617
635
|
|
|
618
636
|
const docPhaseEl = (
|
|
619
637
|
this.template.querySelector("[name=doc-phase]")! as any
|
|
620
|
-
)
|
|
638
|
+
)?.assignedElements?.()[0] as HTMLSlotElement | undefined;
|
|
621
639
|
|
|
622
640
|
const offset = docPhaseEl
|
|
623
641
|
? headerHeight + docPhaseEl.offsetHeight
|
|
@@ -626,10 +644,11 @@ export default class ContentLayout extends LightningElement {
|
|
|
626
644
|
for (const headingElement of headingElements as any) {
|
|
627
645
|
if (headingElement.getAttribute("id") === hash) {
|
|
628
646
|
this.scrollIntoViewWithOffset(headingElement, offset);
|
|
629
|
-
|
|
647
|
+
return true;
|
|
630
648
|
}
|
|
631
649
|
}
|
|
632
650
|
}
|
|
651
|
+
return false;
|
|
633
652
|
}
|
|
634
653
|
|
|
635
654
|
protected scrollIntoViewWithOffset(
|
|
@@ -4,22 +4,11 @@ import DocPhase from "doc/phase";
|
|
|
4
4
|
import DxFooter from "dx/footer";
|
|
5
5
|
import DxIcon from "dx/icon";
|
|
6
6
|
import SidebarFooterNav from "dx/sidebarFooterNav";
|
|
7
|
-
import VersionPicker from "doc/versionPicker";
|
|
8
7
|
import SprigSurvey from "doc/sprigSurvey";
|
|
9
8
|
import { throttle } from "throttle-debounce";
|
|
10
9
|
import { pollUntil } from "dxUtils/async";
|
|
11
10
|
import { toJson } from "dxUtils/normalizers";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
applyVersionToHref,
|
|
15
|
-
collectSubtreeCanonicalHrefs,
|
|
16
|
-
normalizePath,
|
|
17
|
-
getTopicVersionKey,
|
|
18
|
-
persistTopicVersion,
|
|
19
|
-
toCanonicalHref
|
|
20
|
-
} from "dxUtils/topicVersion";
|
|
21
|
-
import type { VersionedNode } from "dxUtils/topicVersion";
|
|
22
|
-
import type { DocPhaseInfo, OptionWithLink } from "typings/custom";
|
|
11
|
+
import type { OptionWithLink } from "typings/custom";
|
|
23
12
|
|
|
24
13
|
declare global {
|
|
25
14
|
interface Window {
|
|
@@ -44,20 +33,8 @@ type ReferenceItem = {
|
|
|
44
33
|
topic?: ReferenceTopic;
|
|
45
34
|
};
|
|
46
35
|
|
|
47
|
-
/** A selectable spec version. Mirrors `ReferenceVersion` in `doc/amfReference`. */
|
|
48
|
-
type ReferenceVersion = {
|
|
49
|
-
id: string;
|
|
50
|
-
label: string;
|
|
51
|
-
deprecated?: boolean;
|
|
52
|
-
selected?: boolean;
|
|
53
|
-
link: { href: string };
|
|
54
|
-
};
|
|
55
|
-
|
|
56
36
|
type ReferenceConfig = {
|
|
57
37
|
refList: ReferenceItem[];
|
|
58
|
-
versions?: ReferenceVersion[];
|
|
59
|
-
/** Sidebar topic name. Sidebar stores the selected version under this key. */
|
|
60
|
-
versionTopicKey?: string;
|
|
61
38
|
};
|
|
62
39
|
|
|
63
40
|
const SCROLL_THROTTLE_DELAY = 50;
|
|
@@ -65,7 +42,6 @@ const ELEMENT_TIMEOUT = 10000;
|
|
|
65
42
|
const ELEMENT_CHECK_INTERVAL = 100;
|
|
66
43
|
const DEFAULT_PROJECT_TITLE = "All Reference";
|
|
67
44
|
const BACK_TARGET_STORAGE_KEY = "redoc-back-target";
|
|
68
|
-
const SELECTED_VIEW_STORAGE_KEY = "redoc-selected-view";
|
|
69
45
|
const DOCS_PATH_SEGMENT = "docs";
|
|
70
46
|
const DEFAULT_LOCALE = "en-us";
|
|
71
47
|
|
|
@@ -140,29 +116,6 @@ export default class RedocReference extends LightningElement {
|
|
|
140
116
|
*/
|
|
141
117
|
@api headerHref: string | null = null;
|
|
142
118
|
|
|
143
|
-
/**
|
|
144
|
-
* Versioned topic that owns this spec, including its children.
|
|
145
|
-
* Href matches this page, so an older version still has its version
|
|
146
|
-
* in the path.
|
|
147
|
-
*/
|
|
148
|
-
@api
|
|
149
|
-
set parentTopicFamily(value: string | { topics?: VersionedNode[] } | null) {
|
|
150
|
-
try {
|
|
151
|
-
const parsed = toJson(value) as { topics?: VersionedNode[] } | null;
|
|
152
|
-
this._parentTopicFamilyNode = parsed?.topics?.[0] ?? null;
|
|
153
|
-
} catch (error) {
|
|
154
|
-
this._parentTopicFamilyNode = null;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
get parentTopicFamily(): { topics?: VersionedNode[] } | null {
|
|
159
|
-
return this._parentTopicFamilyNode
|
|
160
|
-
? { topics: [this._parentTopicFamilyNode] }
|
|
161
|
-
: null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
private _parentTopicFamilyNode: VersionedNode | null = null;
|
|
165
|
-
|
|
166
119
|
@api
|
|
167
120
|
get contentType(): string {
|
|
168
121
|
return this._contentType;
|
|
@@ -199,40 +152,6 @@ export default class RedocReference extends LightningElement {
|
|
|
199
152
|
return refCount > 1;
|
|
200
153
|
}
|
|
201
154
|
|
|
202
|
-
get versions(): ReferenceVersion[] {
|
|
203
|
-
return this._referenceConfig?.versions ?? [];
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/** The version flagged `selected`, else the first (the latest/GA version). */
|
|
207
|
-
get selectedVersion(): ReferenceVersion | null {
|
|
208
|
-
const versions = this.versions;
|
|
209
|
-
return versions.find((v) => v.selected) ?? versions[0] ?? null;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/** True when the selected version is the latest/GA one (index 0). */
|
|
213
|
-
get latestVersion(): boolean {
|
|
214
|
-
return this.versions.length
|
|
215
|
-
? this.selectedVersion?.id === this.versions[0].id
|
|
216
|
-
: false;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
get hasVersionPicker(): boolean {
|
|
220
|
-
return this.versions.length > 0;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** "Newer version available" banner info when on a non-latest version, else null. */
|
|
224
|
-
private get oldVersionInfo(): DocPhaseInfo | null {
|
|
225
|
-
if (this.versions.length > 1 && !this.latestVersion) {
|
|
226
|
-
return oldVersionDocInfo(this.versions[0].link.href);
|
|
227
|
-
}
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/** A single version has nothing to choose, so it renders read-only. */
|
|
232
|
-
get isVersionReadOnly(): boolean {
|
|
233
|
-
return this.versions.length === 1;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
155
|
// Reads stored back target
|
|
237
156
|
private getBackTargetFromSession(): string | null {
|
|
238
157
|
return sessionStorage.getItem(BACK_TARGET_STORAGE_KEY);
|
|
@@ -267,64 +186,13 @@ export default class RedocReference extends LightningElement {
|
|
|
267
186
|
private onBackClick = (event: Event): void => {
|
|
268
187
|
event.preventDefault();
|
|
269
188
|
|
|
270
|
-
const target = this.
|
|
189
|
+
const target = this.getTranslatedBackTarget() ?? this.headerHref;
|
|
271
190
|
|
|
272
191
|
if (target) {
|
|
273
192
|
window.location.assign(target);
|
|
274
193
|
}
|
|
275
194
|
};
|
|
276
195
|
|
|
277
|
-
/**
|
|
278
|
-
* When the back target is in this spec's topic, return it on the
|
|
279
|
-
* selected version. With no back target, do the same for the header
|
|
280
|
-
* link when that link is the versioned parent.
|
|
281
|
-
*/
|
|
282
|
-
private getVersionedBackTarget(): string | null {
|
|
283
|
-
const stored = this.getTranslatedBackTarget();
|
|
284
|
-
const selectedVersion = this.selectedVersion;
|
|
285
|
-
const target = stored ?? this.headerHref;
|
|
286
|
-
if (!target || !selectedVersion) {
|
|
287
|
-
return stored;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
let targetUrl: URL;
|
|
291
|
-
try {
|
|
292
|
-
targetUrl = new URL(target, window.location.origin);
|
|
293
|
-
} catch {
|
|
294
|
-
return target;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Family hrefs match this page's version. Compare them without
|
|
298
|
-
// the version segment. Rewrite only the pathname so the target's
|
|
299
|
-
// query and hash survive.
|
|
300
|
-
const familyHrefs = this._parentTopicFamilyNode
|
|
301
|
-
? collectSubtreeCanonicalHrefs(this._parentTopicFamilyNode).map(
|
|
302
|
-
(href) => toCanonicalHref(href, this.versions)
|
|
303
|
-
)
|
|
304
|
-
: [];
|
|
305
|
-
|
|
306
|
-
const canonicalTarget = toCanonicalHref(
|
|
307
|
-
targetUrl.pathname,
|
|
308
|
-
this.versions
|
|
309
|
-
);
|
|
310
|
-
const inFamily = familyHrefs.some(
|
|
311
|
-
(href) => normalizePath(href) === normalizePath(canonicalTarget)
|
|
312
|
-
);
|
|
313
|
-
if (!inFamily) {
|
|
314
|
-
return target;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const versioned = applyVersionToHref(
|
|
318
|
-
canonicalTarget,
|
|
319
|
-
selectedVersion,
|
|
320
|
-
this.versions
|
|
321
|
-
);
|
|
322
|
-
if (!versioned) {
|
|
323
|
-
return target;
|
|
324
|
-
}
|
|
325
|
-
return `${versioned}${targetUrl.search}${targetUrl.hash}`;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
196
|
private getTranslatedBackTarget(): string | null {
|
|
329
197
|
const stored = this.getBackTargetFromSession();
|
|
330
198
|
if (stored) {
|
|
@@ -334,9 +202,7 @@ export default class RedocReference extends LightningElement {
|
|
|
334
202
|
}
|
|
335
203
|
|
|
336
204
|
const referrer = this.getSameOriginReferrerHref();
|
|
337
|
-
return referrer &&
|
|
338
|
-
!this.isLocaleHref(new URL(referrer).pathname) &&
|
|
339
|
-
!this.isVersionHref(new URL(referrer).pathname)
|
|
205
|
+
return referrer && !this.isLocaleHref(new URL(referrer).pathname)
|
|
340
206
|
? referrer
|
|
341
207
|
: null;
|
|
342
208
|
}
|
|
@@ -375,10 +241,7 @@ export default class RedocReference extends LightningElement {
|
|
|
375
241
|
}
|
|
376
242
|
|
|
377
243
|
const referrerUrl = new URL(referrerHref);
|
|
378
|
-
if (
|
|
379
|
-
this.isLocaleHref(referrerUrl.pathname) ||
|
|
380
|
-
this.isVersionHref(referrerUrl.pathname)
|
|
381
|
-
) {
|
|
244
|
+
if (this.isLocaleHref(referrerUrl.pathname)) {
|
|
382
245
|
return;
|
|
383
246
|
}
|
|
384
247
|
|
|
@@ -419,19 +282,6 @@ export default class RedocReference extends LightningElement {
|
|
|
419
282
|
});
|
|
420
283
|
}
|
|
421
284
|
|
|
422
|
-
/**
|
|
423
|
-
* switching version on this same page.
|
|
424
|
-
*/
|
|
425
|
-
private isVersionHref(pathname: string): boolean {
|
|
426
|
-
return this.versions.some((version) => {
|
|
427
|
-
const href = version?.link?.href;
|
|
428
|
-
return (
|
|
429
|
-
!!href &&
|
|
430
|
-
new URL(href, window.location.origin).pathname === pathname
|
|
431
|
-
);
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
|
|
435
285
|
/** When origin is provided, pass it to the footer; otherwise use dx-footer's default. */
|
|
436
286
|
get effectiveFooterOrigin(): string {
|
|
437
287
|
return (
|
|
@@ -447,13 +297,6 @@ export default class RedocReference extends LightningElement {
|
|
|
447
297
|
// Track the back target, preserving it across locale switches
|
|
448
298
|
this.trackBackTarget();
|
|
449
299
|
|
|
450
|
-
// Remember the selected version for the sidebar. Changing version
|
|
451
|
-
// reloads this page.
|
|
452
|
-
const topicKey = this.getParentTopicVersionKey();
|
|
453
|
-
if (topicKey && this.selectedVersion) {
|
|
454
|
-
persistTopicVersion(topicKey, this.selectedVersion.id);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
300
|
window.addEventListener("scroll", this.handleScrollAndResize);
|
|
458
301
|
window.addEventListener("resize", this.handleScrollAndResize);
|
|
459
302
|
}
|
|
@@ -511,14 +354,6 @@ export default class RedocReference extends LightningElement {
|
|
|
511
354
|
return parseInt(value, 10) || 0;
|
|
512
355
|
}
|
|
513
356
|
|
|
514
|
-
/** Redoc's LNB background color, themeable via `--doc-c-redoc-sidebar-bg`. */
|
|
515
|
-
private get sidebarBackgroundColor(): string {
|
|
516
|
-
const value = getComputedStyle(this.template.host).getPropertyValue(
|
|
517
|
-
"--doc-c-redoc-sidebar-bg"
|
|
518
|
-
);
|
|
519
|
-
return value.trim() || "white";
|
|
520
|
-
}
|
|
521
|
-
|
|
522
357
|
/*
|
|
523
358
|
** Since we could not use --dx-g-global-header-height as getPropertyValue returns a calc expression,
|
|
524
359
|
** we are using the respective CSS variables to calculate the height.
|
|
@@ -634,14 +469,7 @@ export default class RedocReference extends LightningElement {
|
|
|
634
469
|
specUrl,
|
|
635
470
|
{
|
|
636
471
|
// Dynamic scroll offset to account for headers
|
|
637
|
-
scrollYOffset: this.calculateScrollYOffset
|
|
638
|
-
// Redoc's own styled background outranks injected
|
|
639
|
-
// CSS, so the LNB background must be set via theme.
|
|
640
|
-
theme: {
|
|
641
|
-
sidebar: {
|
|
642
|
-
backgroundColor: this.sidebarBackgroundColor
|
|
643
|
-
}
|
|
644
|
-
}
|
|
472
|
+
scrollYOffset: this.calculateScrollYOffset
|
|
645
473
|
},
|
|
646
474
|
redocContainer,
|
|
647
475
|
(error: any) => {
|
|
@@ -687,7 +515,10 @@ export default class RedocReference extends LightningElement {
|
|
|
687
515
|
const apiContentDiv = await this.waitForApiContent(redocContainer);
|
|
688
516
|
apiContentDiv.setAttribute("lwc:dom", "manual");
|
|
689
517
|
|
|
690
|
-
this.
|
|
518
|
+
const docPhaseInfo = this.getDocPhaseInfo();
|
|
519
|
+
if (docPhaseInfo) {
|
|
520
|
+
this.insertDocPhase(apiContentDiv, docPhaseInfo);
|
|
521
|
+
}
|
|
691
522
|
|
|
692
523
|
this.appendFooterItems(apiContentDiv);
|
|
693
524
|
|
|
@@ -696,22 +527,10 @@ export default class RedocReference extends LightningElement {
|
|
|
696
527
|
|
|
697
528
|
// Wait for footer to be rendered before updating styles
|
|
698
529
|
requestAnimationFrame(() => {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
// after all layout mutations (footer, header) are
|
|
704
|
-
// complete, so the scroll lands on the correct element.
|
|
705
|
-
this.restoreSelectedView();
|
|
706
|
-
|
|
707
|
-
// Fix initial hash scroll after doc phase insertion
|
|
708
|
-
this.handleInitialHashScrollFix();
|
|
709
|
-
} catch (error) {
|
|
710
|
-
this.showErrorUI(
|
|
711
|
-
"Failed to integrate custom components:",
|
|
712
|
-
error
|
|
713
|
-
);
|
|
714
|
-
}
|
|
530
|
+
this.updateRedocThirdColumnStyle(redocContainer);
|
|
531
|
+
|
|
532
|
+
// Fix initial hash scroll after doc phase insertion
|
|
533
|
+
this.handleInitialHashScrollFix();
|
|
715
534
|
});
|
|
716
535
|
} catch (error) {
|
|
717
536
|
this.showErrorUI("Failed to integrate custom components:", error);
|
|
@@ -741,65 +560,6 @@ export default class RedocReference extends LightningElement {
|
|
|
741
560
|
}
|
|
742
561
|
}
|
|
743
562
|
|
|
744
|
-
/** Builds the version picker DOM by reusing `doc-version-picker`. */
|
|
745
|
-
private buildVersionPickerDom(): HTMLElement {
|
|
746
|
-
const wrapper = document.createElement("div");
|
|
747
|
-
wrapper.className = "redoc-version-picker";
|
|
748
|
-
|
|
749
|
-
const picker = createElement("doc-version-picker", {
|
|
750
|
-
is: VersionPicker
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
Object.assign(picker, {
|
|
754
|
-
versions: this.versions,
|
|
755
|
-
selectedVersion: this.selectedVersion,
|
|
756
|
-
latestVersion: this.latestVersion,
|
|
757
|
-
readOnly: this.isVersionReadOnly
|
|
758
|
-
});
|
|
759
|
-
picker.addEventListener("change", this.onVersionChange);
|
|
760
|
-
wrapper.appendChild(picker);
|
|
761
|
-
|
|
762
|
-
return wrapper;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
/**
|
|
766
|
-
* Key the sidebar uses for this topic's selected version.
|
|
767
|
-
* Missing `versionTopicKey` returns null so nothing is written.
|
|
768
|
-
*/
|
|
769
|
-
private getParentTopicVersionKey(): string | null {
|
|
770
|
-
const versionTopicKey = this._referenceConfig?.versionTopicKey;
|
|
771
|
-
if (!versionTopicKey) {
|
|
772
|
-
return null;
|
|
773
|
-
}
|
|
774
|
-
return getTopicVersionKey({ name: versionTopicKey }) || null;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
/** Stashes the current view (URL hash) before the version link navigates. */
|
|
778
|
-
private onVersionChange = (): void => {
|
|
779
|
-
sessionStorage.setItem(
|
|
780
|
-
SELECTED_VIEW_STORAGE_KEY,
|
|
781
|
-
window.location.hash || ""
|
|
782
|
-
);
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* Re-applies the view stashed before a version switch, if its anchor exists
|
|
787
|
-
* in the new spec; otherwise a no-op (lands on the spec root).
|
|
788
|
-
*/
|
|
789
|
-
private restoreSelectedView(): void {
|
|
790
|
-
const storedHash = sessionStorage.getItem(SELECTED_VIEW_STORAGE_KEY);
|
|
791
|
-
sessionStorage.removeItem(SELECTED_VIEW_STORAGE_KEY);
|
|
792
|
-
|
|
793
|
-
if (!storedHash || window.location.hash) {
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
const targetId = storedHash.replace(/^#/, "");
|
|
798
|
-
if (targetId && document.getElementById(targetId)) {
|
|
799
|
-
window.location.hash = storedHash;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
563
|
/**
|
|
804
564
|
* Builds the locale picker DOM by reusing `dx-sidebar-footer-nav`
|
|
805
565
|
*/
|
|
@@ -821,17 +581,12 @@ export default class RedocReference extends LightningElement {
|
|
|
821
581
|
}
|
|
822
582
|
|
|
823
583
|
/**
|
|
824
|
-
* Builds
|
|
825
|
-
* versions are available, the version picker as a sibling row. Layout/gaps
|
|
826
|
-
* are styled by the developer-website's redoc CSS.
|
|
584
|
+
* Builds a fresh project-title/spec-title header DOM node.
|
|
827
585
|
*/
|
|
828
586
|
private buildProjectHeaderDom(): HTMLElement {
|
|
829
587
|
const wrapper = document.createElement("div");
|
|
830
588
|
wrapper.className = "redoc-project-header";
|
|
831
589
|
|
|
832
|
-
const main = document.createElement("div");
|
|
833
|
-
main.className = "redoc-project-header-main";
|
|
834
|
-
|
|
835
590
|
if (this.projectTitle) {
|
|
836
591
|
const backLink = document.createElement("a");
|
|
837
592
|
backLink.className = "redoc-project-back";
|
|
@@ -852,20 +607,14 @@ export default class RedocReference extends LightningElement {
|
|
|
852
607
|
|
|
853
608
|
backLink.appendChild(icon);
|
|
854
609
|
backLink.appendChild(label);
|
|
855
|
-
|
|
610
|
+
wrapper.appendChild(backLink);
|
|
856
611
|
}
|
|
857
612
|
|
|
858
613
|
if (this.specTitle) {
|
|
859
614
|
const specEl = document.createElement("h2");
|
|
860
615
|
specEl.className = "redoc-spec-title dx-text-display-7";
|
|
861
616
|
specEl.textContent = this.specTitle;
|
|
862
|
-
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
wrapper.appendChild(main);
|
|
866
|
-
|
|
867
|
-
if (this.hasVersionPicker) {
|
|
868
|
-
wrapper.appendChild(this.buildVersionPickerDom());
|
|
617
|
+
wrapper.appendChild(specEl);
|
|
869
618
|
}
|
|
870
619
|
|
|
871
620
|
return wrapper;
|
|
@@ -890,40 +639,15 @@ export default class RedocReference extends LightningElement {
|
|
|
890
639
|
return container.querySelector<HTMLElement>(".api-content")!;
|
|
891
640
|
}
|
|
892
641
|
|
|
893
|
-
//
|
|
894
|
-
|
|
895
|
-
private insertStatusItems(container: HTMLElement): void {
|
|
896
|
-
const docPhaseInfo = this.getDocPhaseInfo();
|
|
897
|
-
const oldVersionInfo = this.oldVersionInfo;
|
|
898
|
-
if (!docPhaseInfo && !oldVersionInfo) {
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
|
|
642
|
+
// Creates and inserts doc phase component at container start
|
|
643
|
+
private insertDocPhase(container: HTMLElement, docPhaseInfo: string): void {
|
|
902
644
|
const wrapper = document.createElement("div");
|
|
903
645
|
wrapper.className = "doc-phase-wrapper";
|
|
904
646
|
container.insertBefore(wrapper, container.firstChild);
|
|
905
647
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
});
|
|
910
|
-
Object.assign(docPhaseElement, { docPhaseInfo });
|
|
911
|
-
wrapper.appendChild(docPhaseElement);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
if (oldVersionInfo) {
|
|
915
|
-
const versionBanner = createElement("doc-phase", { is: DocPhase });
|
|
916
|
-
Object.assign(versionBanner, {
|
|
917
|
-
docPhaseInfo: oldVersionInfo,
|
|
918
|
-
dismissible: true,
|
|
919
|
-
iconName: "warning"
|
|
920
|
-
});
|
|
921
|
-
versionBanner.addEventListener("dismissphase", () => {
|
|
922
|
-
versionBanner.remove();
|
|
923
|
-
this.updateSidebarPosition();
|
|
924
|
-
});
|
|
925
|
-
wrapper.appendChild(versionBanner);
|
|
926
|
-
}
|
|
648
|
+
const docPhaseElement = createElement("doc-phase", { is: DocPhase });
|
|
649
|
+
Object.assign(docPhaseElement, { docPhaseInfo });
|
|
650
|
+
wrapper.appendChild(docPhaseElement);
|
|
927
651
|
}
|
|
928
652
|
|
|
929
653
|
// Appends footer component to container
|
|
@@ -24,14 +24,6 @@
|
|
|
24
24
|
lwc:if={docPhaseInfo}
|
|
25
25
|
doc-phase-info={docPhaseInfo}
|
|
26
26
|
></doc-phase>
|
|
27
|
-
<doc-phase
|
|
28
|
-
slot="version-banner"
|
|
29
|
-
lwc:if={showVersionBanner}
|
|
30
|
-
doc-phase-info={oldVersionInfo}
|
|
31
|
-
icon-name="warning"
|
|
32
|
-
dismissible="true"
|
|
33
|
-
ondismissphase={handleDismissVersionBanner}
|
|
34
|
-
></doc-phase>
|
|
35
27
|
<slot></slot>
|
|
36
28
|
</doc-content-layout>
|
|
37
29
|
</template>
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import { LightningElement, api
|
|
1
|
+
import { LightningElement, api } from "lwc";
|
|
2
2
|
import { toJson } from "dxUtils/normalizers";
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
5
|
-
findTopicVersionForCurrentPage,
|
|
6
|
-
latestHrefForCurrentPage
|
|
7
|
-
} from "dxUtils/topicVersion";
|
|
8
|
-
import { oldVersionDocInfo } from "docUtils/utils";
|
|
9
|
-
import type { DocPhaseInfo, OptionWithLink, TreeNode } from "typings/custom";
|
|
3
|
+
import type { OptionWithLink, TreeNode } from "typings/custom";
|
|
10
4
|
|
|
11
5
|
/**
|
|
12
6
|
* Per-topic type emitted by the docs content-type parser
|
|
@@ -16,38 +10,61 @@ import type { DocPhaseInfo, OptionWithLink, TreeNode } from "typings/custom";
|
|
|
16
10
|
*/
|
|
17
11
|
const TOPIC_TYPE_SPEC = "spec";
|
|
18
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Walks down the first-child chain and returns the first markdown content
|
|
15
|
+
* page found, descending through nested content-less parents. Returns
|
|
16
|
+
* undefined if the chain breaks on a spec or external link before reaching a
|
|
17
|
+
* content page.
|
|
18
|
+
*/
|
|
19
|
+
function firstChildContentPage(node: TreeNode): TreeNode | undefined {
|
|
20
|
+
const firstChild = node.children?.[0];
|
|
21
|
+
if (!firstChild) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const isSpec =
|
|
26
|
+
(firstChild as { topicType?: string }).topicType === TOPIC_TYPE_SPEC;
|
|
27
|
+
const isExternal = firstChild.link?.target === "_blank";
|
|
28
|
+
if (isSpec || isExternal) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (firstChild.link?.href) {
|
|
33
|
+
return firstChild;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return firstChildContentPage(firstChild);
|
|
37
|
+
}
|
|
38
|
+
|
|
19
39
|
/**
|
|
20
40
|
* Translates per-topic `topicType` into the generic `showForwardArrow` flag
|
|
21
|
-
* that `dx-tree-tile` reads (spec topics get the forward arrow icon for Redoc)
|
|
22
|
-
*
|
|
23
|
-
* Content-less parents are flagged with redirectToChild and left without a
|
|
24
|
-
* link. The tree selects the first markdown descendant. Copying that child's
|
|
25
|
-
* href onto the parent makes the parent win selection.
|
|
41
|
+
* that `dx-tree-tile` reads (spec topics get the forward arrow icon for Redoc),
|
|
42
|
+
* and redirects content-less parent topics whose first child is a content page
|
|
26
43
|
*/
|
|
27
|
-
function
|
|
28
|
-
topics:
|
|
29
|
-
| Array<TreeNode & { topicType?: string; versions?: unknown }>
|
|
30
|
-
| undefined
|
|
44
|
+
function decorateTopics(
|
|
45
|
+
topics: Array<TreeNode & { topicType?: string }> | undefined
|
|
31
46
|
): TreeNode[] | undefined {
|
|
32
47
|
return topics?.map((topic) => {
|
|
33
|
-
const { topicType,
|
|
48
|
+
const { topicType, ...decorated } = topic;
|
|
34
49
|
if (topicType === TOPIC_TYPE_SPEC) {
|
|
35
50
|
decorated.showForwardArrow = true;
|
|
36
|
-
// Nested and standalone specs never get a sidebar picker; only the
|
|
37
|
-
// parent MD topic (when versioned) shows one.
|
|
38
|
-
} else if (versions) {
|
|
39
|
-
decorated.versions = versions;
|
|
40
51
|
}
|
|
41
52
|
if (decorated.children) {
|
|
42
|
-
decorated.children =
|
|
43
|
-
decorated.children
|
|
44
|
-
);
|
|
53
|
+
decorated.children = decorateTopics(decorated.children);
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
/** Redirect a parent that has no content of its own to its first child
|
|
57
|
+
* but only when that first child is itself a content page
|
|
58
|
+
*/
|
|
59
|
+
const isContentlessParent =
|
|
60
|
+
!!decorated.children?.length && !decorated.link?.href;
|
|
61
|
+
|
|
62
|
+
if (isContentlessParent) {
|
|
50
63
|
decorated.redirectToChild = true;
|
|
64
|
+
const firstChild = firstChildContentPage(topic);
|
|
65
|
+
if (firstChild?.link) {
|
|
66
|
+
decorated.link = { ...firstChild.link };
|
|
67
|
+
}
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
return decorated;
|
|
@@ -84,9 +101,6 @@ export default class UnifiedContentLayout extends LightningElement {
|
|
|
84
101
|
|
|
85
102
|
private _docPhaseInfo: string | null = null;
|
|
86
103
|
private _sidebarContent: unknown = null;
|
|
87
|
-
private _oldVersionInfo: DocPhaseInfo | null = null;
|
|
88
|
-
|
|
89
|
-
@track showVersionBanner = false;
|
|
90
104
|
|
|
91
105
|
@api
|
|
92
106
|
get docPhaseInfo(): string | null {
|
|
@@ -103,40 +117,7 @@ export default class UnifiedContentLayout extends LightningElement {
|
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
set sidebarContent(value: string) {
|
|
106
|
-
|
|
107
|
-
this._sidebarContent = topics;
|
|
108
|
-
this.updateTopicVersionBanner(topics as VersionedNode[] | undefined);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
get oldVersionInfo(): DocPhaseInfo | null {
|
|
112
|
-
return this._oldVersionInfo;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
handleDismissVersionBanner(): void {
|
|
116
|
-
this.showVersionBanner = false;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Same banner references use when the reader is on an older version.
|
|
121
|
-
* Topic versions are not a separate page property: the sidebar tree
|
|
122
|
-
* carries each version's switching URL, and the current pathname says
|
|
123
|
-
* which one is open.
|
|
124
|
-
*/
|
|
125
|
-
private updateTopicVersionBanner(
|
|
126
|
-
topics: VersionedNode[] | undefined
|
|
127
|
-
): void {
|
|
128
|
-
const match = findTopicVersionForCurrentPage(topics);
|
|
129
|
-
const latest = match?.node.versions?.[0];
|
|
130
|
-
const latestHref = latestHrefForCurrentPage(match?.node.versions);
|
|
131
|
-
|
|
132
|
-
if (match && latest && match.version.id !== latest.id && latestHref) {
|
|
133
|
-
this._oldVersionInfo = oldVersionDocInfo(latestHref);
|
|
134
|
-
this.showVersionBanner = true;
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
this._oldVersionInfo = null;
|
|
139
|
-
this.showVersionBanner = false;
|
|
120
|
+
this._sidebarContent = decorateTopics(toJson(value)?.topics);
|
|
140
121
|
}
|
|
141
122
|
|
|
142
123
|
private get enableFooter(): boolean {
|
|
@@ -2,69 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
/* NOTE: doc-version-picker-width width variable is used by both dx-button and dx-dropdown to maintain a consistent width. */
|
|
4
4
|
:host {
|
|
5
|
-
/* Always positioned so the popover's containing block stays the picker.
|
|
6
|
-
Toggling position with aria-expanded lets the menu jump to the sidebar
|
|
7
|
-
(top of the ToC) for a frame before LWC hides it. */
|
|
8
|
-
position: relative;
|
|
9
|
-
|
|
10
5
|
--dx-c-dropdown-option-font-weight: normal;
|
|
11
|
-
--dx-c-dropdown-option-font-size: var(--dx-g-text-base);
|
|
12
6
|
--dx-c-dropdown-option-label-color: var(--dx-g-gray-10);
|
|
13
|
-
--dx-c-dropdown-option-active-label-color: var(--dx-g-gray-10);
|
|
14
|
-
--dx-c-dropdown-option-padding: var(--dx-g-spacing-sm)
|
|
15
|
-
var(--dx-g-spacing-lg);
|
|
16
|
-
--dx-c-popover-padding: var(--dx-g-spacing-sm) 0;
|
|
17
7
|
--popover-container-open-transform: translateY(4px);
|
|
18
|
-
--dx-c-version-picker-focus-ring: 0 0 0 2px white,
|
|
19
|
-
0 0 0 4px var(--dx-g-blue-vibrant-60);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/* Open menus must stack above later sibling pickers in the sidebar. */
|
|
23
|
-
:host(:has([aria-expanded="true"])) {
|
|
24
|
-
z-index: 2;
|
|
25
8
|
}
|
|
26
9
|
|
|
27
10
|
.version-picker-container {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
var(--dx-g-spacing-sm) var(--dx-g-spacing-lg) var(--dx-g-spacing-sm)
|
|
33
|
-
var(--dx-g-global-header-padding-horizontal)
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
/* Override --doc-version-picker-divider (e.g. `none`) to restyle dividers. */
|
|
37
|
-
border-top: var(
|
|
38
|
-
--doc-version-picker-divider,
|
|
39
|
-
1px solid var(--dx-g-gray-90)
|
|
40
|
-
);
|
|
41
|
-
border-bottom: var(
|
|
42
|
-
--doc-version-picker-divider,
|
|
43
|
-
1px solid var(--dx-g-gray-90)
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/* Inline sidebar variant: drop the header-slot chrome so the picker sits
|
|
48
|
-
beside a tree node without extra padding or divider lines. */
|
|
49
|
-
.version-picker-container-small {
|
|
50
|
-
display: flex;
|
|
51
|
-
align-items: center;
|
|
52
|
-
padding: 0;
|
|
53
|
-
border: none;
|
|
11
|
+
padding: 8px var(--dx-g-spacing-lg) 8px
|
|
12
|
+
var(--dx-g-global-header-padding-horizontal);
|
|
13
|
+
border-top: 1px solid var(--dx-g-gray-90);
|
|
14
|
+
border-bottom: 1px solid var(--dx-g-gray-90);
|
|
54
15
|
}
|
|
55
16
|
|
|
56
17
|
.version-picker-button {
|
|
57
18
|
display: flex;
|
|
58
|
-
width:
|
|
59
|
-
overflow: visible;
|
|
60
|
-
|
|
61
|
-
--dx-c-button-horizontal-spacing: var(--dx-g-spacing-sm);
|
|
62
|
-
--dx-g-button-icon-color: var(--dx-g-gray-50);
|
|
19
|
+
width: var(--doc-version-picker-width, 296px);
|
|
63
20
|
}
|
|
64
21
|
|
|
65
22
|
.version-picker-button:hover,
|
|
66
|
-
.version-picker-button
|
|
67
|
-
|
|
23
|
+
.version-picker-button:active,
|
|
24
|
+
.version-picker-button:focus {
|
|
25
|
+
--dx-c-button-secondary-color-hover: var(--dx-g-cloud-blue-vibrant-95);
|
|
26
|
+
--dx-c-button-primary-color: var(--dx-g-blue-vibrant-40);
|
|
68
27
|
}
|
|
69
28
|
|
|
70
29
|
/**
|
|
@@ -76,57 +35,6 @@ dx-button::part(content) {
|
|
|
76
35
|
overflow: hidden;
|
|
77
36
|
}
|
|
78
37
|
|
|
79
|
-
/* Border width stays 1px in every state. Hover, open, and focus paint the
|
|
80
|
-
2px blue stroke with an inset shadow so the box does not grow.
|
|
81
|
-
Tertiary's hover border is overridden. */
|
|
82
|
-
.version-picker-button::part(container) {
|
|
83
|
-
box-sizing: border-box;
|
|
84
|
-
background: white;
|
|
85
|
-
border: 1px solid var(--dx-g-gray-50);
|
|
86
|
-
border-radius: var(--dx-g-spacing-xs);
|
|
87
|
-
box-shadow: none;
|
|
88
|
-
color: var(--dx-g-gray-10);
|
|
89
|
-
outline: none;
|
|
90
|
-
transition: var(--dx-g-transition-hue-1x), box-shadow 0.15s linear;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/* Hover only (not open): 2px #032D60 on #EEF4FF, no ring.
|
|
94
|
-
Border color matches the fill so the 1px border does not add a third
|
|
95
|
-
pixel around the inset stroke.
|
|
96
|
-
:focus-visible is included so click-open + Escape (which upgrades the
|
|
97
|
-
trigger to keyboard focus) still looks like hover while the pointer is on
|
|
98
|
-
the picker. */
|
|
99
|
-
.version-picker-button:hover:not([aria-expanded="true"])::part(container),
|
|
100
|
-
.version-picker-button:hover:not([aria-expanded="true"])::part(
|
|
101
|
-
container
|
|
102
|
-
):focus-visible {
|
|
103
|
-
background: var(--dx-g-blue-vibrant-95);
|
|
104
|
-
border: 1px solid var(--dx-g-blue-vibrant-95);
|
|
105
|
-
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/* Open keeps the fill and the 2px stroke. The focus ring is keyboard-only,
|
|
109
|
-
on the closed picker below. */
|
|
110
|
-
.version-picker-button[aria-expanded="true"]::part(container) {
|
|
111
|
-
background: var(--dx-g-blue-vibrant-95);
|
|
112
|
-
border: 1px solid var(--dx-g-blue-vibrant-95);
|
|
113
|
-
border-radius: var(--dx-g-spacing-xs);
|
|
114
|
-
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/* Keyboard focus while closed. */
|
|
118
|
-
.version-picker-button:not([aria-expanded="true"])::part(
|
|
119
|
-
container
|
|
120
|
-
):focus-visible {
|
|
121
|
-
--dx-g-button-icon-color: var(--dx-g-blue-vibrant-20);
|
|
122
|
-
|
|
123
|
-
background: var(--dx-g-blue-vibrant-95);
|
|
124
|
-
border: 1px solid var(--dx-g-blue-vibrant-95);
|
|
125
|
-
border-radius: var(--dx-g-spacing-xs);
|
|
126
|
-
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20),
|
|
127
|
-
var(--dx-c-version-picker-focus-ring);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
38
|
.selected-version {
|
|
131
39
|
display: flex;
|
|
132
40
|
flex-direction: row;
|
|
@@ -141,111 +49,16 @@ dx-button::part(content) {
|
|
|
141
49
|
white-space: nowrap;
|
|
142
50
|
}
|
|
143
51
|
|
|
144
|
-
dx-type-badge.latest-badge
|
|
145
|
-
dx-type-badge
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/* ------------------------------------------------------------------ *
|
|
150
|
-
* Small "dot" variant (opt-in via `small`). Additive
|
|
151
|
-
* ------------------------------------------------------------------ */
|
|
152
|
-
|
|
153
|
-
/* Menu is capped at the ToC width (296px), not the 104px trigger, so options
|
|
154
|
-
keep the same 16px / 8px 24px / gray-10 type as the default picker. */
|
|
155
|
-
.version-picker-dropdown-small {
|
|
156
|
-
--dx-c-dropdown-option-font-size: var(--dx-g-text-base);
|
|
157
|
-
--dx-c-dropdown-option-padding: var(--dx-g-spacing-sm)
|
|
158
|
-
var(--dx-g-spacing-lg);
|
|
159
|
-
--dx-c-dropdown-option-label-color: var(--dx-g-gray-10);
|
|
160
|
-
--dx-c-dropdown-option-border-radius: 0;
|
|
161
|
-
--dx-c-popover-border: none;
|
|
162
|
-
|
|
163
|
-
/* Hug the labels (down to the shortest) but never wider than the ToC
|
|
164
|
-
content: 296px on desktop, and on the smallest breakpoint the sidebar
|
|
165
|
-
variable grows with the viewport up to 720px. Long labels wrap inside
|
|
166
|
-
that cap. */
|
|
167
|
-
--dx-c-dropdown-menu-max-width: var(--doc-version-picker-width, 296px);
|
|
168
|
-
--dx-c-dropdown-fit-white-space: normal;
|
|
169
|
-
--dx-c-dropdown-fit-overflow: hidden;
|
|
170
|
-
--dx-c-dropdown-fit-text-overflow: clip;
|
|
171
|
-
--dx-c-dropdown-option-overflow-wrap: anywhere;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.version-picker-button-small {
|
|
175
|
-
width: fit-content;
|
|
176
|
-
max-width: 104px;
|
|
177
|
-
|
|
178
|
-
--dx-c-button-font-size: var(--dx-g-text-xs);
|
|
179
|
-
--dx-c-button-font-weight: var(--dx-g-font-normal);
|
|
180
|
-
--dx-c-button-line-height: var(--dx-g-spacing-lg);
|
|
181
|
-
--dx-c-button-icon-gap: var(--dx-g-spacing-2xs);
|
|
182
|
-
}
|
|
52
|
+
dx-type-badge.latest-badge {
|
|
53
|
+
--dx-c-type-badge-color: var(--dx-g-green-vibrant-40);
|
|
54
|
+
--dx-c-type-badge-background: var(--dx-g-green-vibrant-95);
|
|
183
55
|
|
|
184
|
-
|
|
185
|
-
default min-width: auto refuses to shrink below its content and overrides the
|
|
186
|
-
host's max-width, so without this the trigger overflows instead. */
|
|
187
|
-
.version-picker-button-small::part(content) {
|
|
188
|
-
display: flex;
|
|
189
|
-
flex: 1;
|
|
190
|
-
min-width: 0;
|
|
191
|
-
width: auto;
|
|
192
|
-
overflow: hidden;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/* width:100% re-ties to the 104px-capped host (container is width:inherit,
|
|
196
|
-
which copies width but not max-width). Border/bg/states are shared above. */
|
|
197
|
-
.version-picker-button-small::part(container) {
|
|
198
|
-
width: 100%;
|
|
199
|
-
height: var(--dx-g-spacing-lg);
|
|
200
|
-
padding: 0 var(--dx-g-spacing-xs);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.selected-version-small {
|
|
204
|
-
flex: 1;
|
|
205
|
-
min-width: 0;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.version-picker-dot {
|
|
209
|
-
flex: 0 0 auto;
|
|
210
|
-
width: var(--dx-g-spacing-sm);
|
|
211
|
-
height: var(--dx-g-spacing-sm);
|
|
212
|
-
margin-right: var(--dx-g-spacing-xs);
|
|
213
|
-
border-radius: 50%;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.version-picker-dot-latest {
|
|
217
|
-
background: var(--dx-g-green-vibrant-60);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.version-picker-dot-not-latest {
|
|
221
|
-
background: var(--dx-g-yellow-vibrant-80);
|
|
56
|
+
margin-left: var(--dx-g-spacing-sm);
|
|
222
57
|
}
|
|
223
58
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
display: inline-block;
|
|
228
|
-
width: 100%;
|
|
229
|
-
height: var(--dx-g-spacing-xl);
|
|
230
|
-
margin: 0;
|
|
231
|
-
padding: 0 var(--dx-g-spacing-sm);
|
|
232
|
-
border: 1px solid var(--dx-g-gray-80);
|
|
233
|
-
border-radius: var(--dx-g-spacing-xs);
|
|
234
|
-
overflow: hidden;
|
|
235
|
-
color: var(--dx-g-gray-10);
|
|
236
|
-
text-overflow: ellipsis;
|
|
237
|
-
white-space: nowrap;
|
|
238
|
-
font: var(--dx-g-font-normal) var(--dx-g-text-sm) / var(--dx-g-spacing-xl)
|
|
239
|
-
var(--dx-g-font-sans),
|
|
240
|
-
sans-serif;
|
|
241
|
-
}
|
|
59
|
+
dx-type-badge.not-latest-badge {
|
|
60
|
+
--dx-c-type-badge-color: var(--dx-g-red-vibrant-40);
|
|
61
|
+
--dx-c-type-badge-background: var(--dx-g-red-vibrant-95);
|
|
242
62
|
|
|
243
|
-
|
|
244
|
-
width: fit-content;
|
|
245
|
-
max-width: 104px;
|
|
246
|
-
height: var(--dx-g-spacing-lg);
|
|
247
|
-
padding: 0 var(--dx-g-spacing-xs);
|
|
248
|
-
font: var(--dx-g-font-normal) var(--dx-g-text-xs) / var(--dx-g-spacing-lg)
|
|
249
|
-
var(--dx-g-font-sans),
|
|
250
|
-
sans-serif;
|
|
63
|
+
margin-left: var(--dx-g-spacing-sm);
|
|
251
64
|
}
|
|
@@ -1,70 +1,37 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div lwc:if={showVersionPicker} class=
|
|
3
|
-
<!-- Small read-only: plain value, no dropdown -->
|
|
4
|
-
<p
|
|
5
|
-
lwc:if={readOnly}
|
|
6
|
-
class={readOnlyClass}
|
|
7
|
-
title={selectedVersion.label}
|
|
8
|
-
>
|
|
9
|
-
{selectedVersion.label}
|
|
10
|
-
</p>
|
|
11
|
-
|
|
2
|
+
<div lwc:if={showVersionPicker} class="version-picker-container">
|
|
12
3
|
<dx-dropdown
|
|
13
|
-
lwc:else
|
|
14
|
-
class={dropdownClass}
|
|
15
4
|
options={versions}
|
|
16
5
|
analytics-event="custEv_docVersionSelect"
|
|
17
6
|
analytics-payload={analyticsPayload}
|
|
18
7
|
value={selectedVersion.id}
|
|
19
|
-
width-
|
|
8
|
+
width="var(--doc-version-picker-width)"
|
|
20
9
|
onchange={onVersionChange}
|
|
21
10
|
>
|
|
22
11
|
<dx-button
|
|
23
|
-
class=
|
|
12
|
+
class="version-picker-button"
|
|
24
13
|
variant="tertiary"
|
|
25
14
|
size="small"
|
|
26
|
-
font="sans"
|
|
27
15
|
icon-symbol="chevrondown"
|
|
28
|
-
icon-size=
|
|
29
|
-
aria-label={triggerAriaLabel}
|
|
16
|
+
icon-size="medium"
|
|
30
17
|
>
|
|
31
|
-
<div class=
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
title={selectedVersion.label}
|
|
49
|
-
>
|
|
50
|
-
{selectedVersion.label}
|
|
51
|
-
</p>
|
|
52
|
-
<template lwc:if={showLatestTag}>
|
|
53
|
-
<dx-type-badge
|
|
54
|
-
class="latest-badge"
|
|
55
|
-
lwc:if={latestVersion}
|
|
56
|
-
variant="status-success"
|
|
57
|
-
value="Latest"
|
|
58
|
-
size="small"
|
|
59
|
-
></dx-type-badge>
|
|
60
|
-
<dx-type-badge
|
|
61
|
-
class="not-latest-badge"
|
|
62
|
-
lwc:else
|
|
63
|
-
variant="status-warning"
|
|
64
|
-
value="Not Latest"
|
|
65
|
-
size="small"
|
|
66
|
-
></dx-type-badge>
|
|
67
|
-
</template>
|
|
18
|
+
<div class="selected-version">
|
|
19
|
+
<p class="selected-version-label">
|
|
20
|
+
{selectedVersion.label}
|
|
21
|
+
</p>
|
|
22
|
+
<template lwc:if={showLatestTag}>
|
|
23
|
+
<dx-type-badge
|
|
24
|
+
class="latest-badge"
|
|
25
|
+
lwc:if={latestVersion}
|
|
26
|
+
value="Latest"
|
|
27
|
+
size="small"
|
|
28
|
+
></dx-type-badge>
|
|
29
|
+
<dx-type-badge
|
|
30
|
+
class="not-latest-badge"
|
|
31
|
+
lwc:else
|
|
32
|
+
value="Not Latest"
|
|
33
|
+
size="small"
|
|
34
|
+
></dx-type-badge>
|
|
68
35
|
</template>
|
|
69
36
|
</div>
|
|
70
37
|
</dx-button>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { LightningElement, api, track } from "lwc";
|
|
2
|
-
import cx from "classnames";
|
|
3
2
|
|
|
4
3
|
import { AnalyticsPayload, OptionWithNested } from "typings/custom";
|
|
5
4
|
|
|
@@ -13,8 +12,6 @@ export default class VersionPicker extends LightningElement {
|
|
|
13
12
|
private _selectedVersion?: OptionWithNested;
|
|
14
13
|
private _latestVersion: boolean = false;
|
|
15
14
|
private _hideBadge: boolean = false;
|
|
16
|
-
private _small: boolean = false;
|
|
17
|
-
private _readOnly: boolean = false;
|
|
18
15
|
|
|
19
16
|
@api
|
|
20
17
|
get versions() {
|
|
@@ -54,103 +51,14 @@ export default class VersionPicker extends LightningElement {
|
|
|
54
51
|
this._hideBadge = normalizeBoolean(value);
|
|
55
52
|
}
|
|
56
53
|
|
|
57
|
-
// Opt-in compact "dot" variant (24px, abbreviated label). Off by default so
|
|
58
|
-
// existing consumers render the default trigger unchanged.
|
|
59
|
-
@api
|
|
60
|
-
get small() {
|
|
61
|
-
return this._small;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
set small(value) {
|
|
65
|
-
this._small = normalizeBoolean(value);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Opt-in read-only state: plain value, no dropdown menu.
|
|
69
|
-
@api
|
|
70
|
-
get readOnly() {
|
|
71
|
-
return this._readOnly;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
set readOnly(value) {
|
|
75
|
-
this._readOnly = normalizeBoolean(value);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
54
|
private get showVersionPicker() {
|
|
79
55
|
return this._versions && this._versions.length !== 0;
|
|
80
56
|
}
|
|
81
57
|
|
|
82
|
-
private get containerClass(): string {
|
|
83
|
-
return this.small
|
|
84
|
-
? "version-picker-container-small"
|
|
85
|
-
: "version-picker-container";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
58
|
private get showLatestTag(): boolean {
|
|
89
59
|
return !this.hideBadge;
|
|
90
60
|
}
|
|
91
61
|
|
|
92
|
-
private get readOnlyClass(): string {
|
|
93
|
-
return cx(
|
|
94
|
-
"version-picker-readonly",
|
|
95
|
-
this.small && "version-picker-readonly-small"
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private get dotClass(): string {
|
|
100
|
-
return `version-picker-dot ${
|
|
101
|
-
this.latestVersion
|
|
102
|
-
? "version-picker-dot-latest"
|
|
103
|
-
: "version-picker-dot-not-latest"
|
|
104
|
-
}`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Accessible name for the small trigger: includes the latest state so it
|
|
108
|
-
// isn't conveyed by the dot's color alone (WCAG 1.4.1). Omits the state
|
|
109
|
-
// when the indicator is hidden.
|
|
110
|
-
private get smallTriggerAriaLabel(): string {
|
|
111
|
-
const label = this.selectedVersion?.label ?? "";
|
|
112
|
-
if (!this.showLatestTag) {
|
|
113
|
-
return label;
|
|
114
|
-
}
|
|
115
|
-
return `${label}, ${this.latestVersion ? "Latest" : "Not Latest"}`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// --- Per-variant values for the shared dropdown/button markup below. The
|
|
119
|
-
// small variant opts into extra classes and a compact chevron.
|
|
120
|
-
|
|
121
|
-
// Not cx(): the default must have NO class attribute (cx returns "", which
|
|
122
|
-
// would render class="" and break the frozen consumer snapshots).
|
|
123
|
-
private get dropdownClass(): string | undefined {
|
|
124
|
-
return this.small ? "version-picker-dropdown-small" : undefined;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// "full" matches the trigger (ToC width). "fit-end" sizes to the labels,
|
|
128
|
-
// caps at the ToC width, and opens inward.
|
|
129
|
-
private get dropdownWidthMode(): "full" | "fit-end" {
|
|
130
|
-
return this.small ? "fit-end" : "full";
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private get triggerClass(): string {
|
|
134
|
-
return cx(
|
|
135
|
-
"version-picker-button",
|
|
136
|
-
this.small && "version-picker-button-small"
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private get triggerIconSize(): string {
|
|
141
|
-
return this.small ? "xsmall" : "medium";
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Default returns "" (dx-button's own default) so the trigger's rendered
|
|
145
|
-
// aria-label is unchanged for the default variant.
|
|
146
|
-
private get triggerAriaLabel(): string {
|
|
147
|
-
return this.small ? this.smallTriggerAriaLabel : "";
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
private get selectedVersionClass(): string {
|
|
151
|
-
return cx("selected-version", this.small && "selected-version-small");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
62
|
private onVersionChange(e: CustomEvent) {
|
|
155
63
|
this.dispatchEvent(new CustomEvent("change", { detail: e.detail }));
|
|
156
64
|
}
|
|
@@ -1,28 +1,10 @@
|
|
|
1
1
|
import { CoveoAnalyticsClient } from "coveo.analytics/dist/browser.mjs"; // This fix is required for Node 20 upgrade, so that Coveo analytics is loaded properly by LWR.
|
|
2
2
|
|
|
3
|
-
// Resolve against the current origin (version links are usually relative) and
|
|
4
|
-
// keep only http/https. Blocks executable schemes like javascript:/data: that
|
|
5
|
-
// would run when the innerHTML-rendered link is clicked
|
|
6
|
-
// encodeURI then keeps the value from breaking out of the href attribute.
|
|
7
|
-
const toSafeHref = (link: string): string => {
|
|
8
|
-
try {
|
|
9
|
-
const { protocol } = new URL(link, window.location.origin);
|
|
10
|
-
if (protocol === "http:" || protocol === "https:") {
|
|
11
|
-
return encodeURI(link);
|
|
12
|
-
}
|
|
13
|
-
} catch {
|
|
14
|
-
// Fall through to the rejection below.
|
|
15
|
-
}
|
|
16
|
-
console.warn(`Blocked unsafe version link: ${link}`);
|
|
17
|
-
return "#";
|
|
18
|
-
};
|
|
19
|
-
|
|
20
3
|
export const oldVersionDocInfo = (latestVersionLink: string) => {
|
|
21
|
-
const safeLink = toSafeHref(latestVersionLink);
|
|
22
4
|
return {
|
|
23
5
|
title: "Newer Version Available",
|
|
24
|
-
body: `This content describes an older version of this product.
|
|
25
|
-
<a style="font-weight: bold;" href="${
|
|
6
|
+
body: `This content describes an older version of this product.
|
|
7
|
+
<a style="font-weight: bold;" href="${latestVersionLink}">View Latest</a>`
|
|
26
8
|
};
|
|
27
9
|
};
|
|
28
10
|
|