@salesforcedevs/docs-components 1.34.3-style-fix-alpha4 → 1.34.4-scroll-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/modules/doc/banner/banner.css +41 -2
- package/src/modules/doc/banner/banner.html +6 -5
- package/src/modules/doc/banner/banner.ts +28 -0
- 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 +20 -192
- 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.
|
|
3
|
+
"version": "1.34.4-scroll-1",
|
|
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
|
}
|
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.icon {
|
|
19
|
-
--dx-c-icon-size: var(--dx-g-icon-size-lg);
|
|
20
|
-
|
|
21
19
|
flex-shrink: 0;
|
|
22
20
|
margin-top: var(--dx-g-spacing-smd);
|
|
23
21
|
margin-right: var(--dx-g-spacing-sm);
|
|
@@ -73,6 +71,47 @@
|
|
|
73
71
|
cursor: pointer;
|
|
74
72
|
}
|
|
75
73
|
|
|
74
|
+
/* Centered variant: the message is centered in the banner and the icon hangs
|
|
75
|
+
to its left. The close button is taken out of the flow so it doesn't offset
|
|
76
|
+
the centering. */
|
|
77
|
+
.container_centered {
|
|
78
|
+
position: relative;
|
|
79
|
+
display: grid;
|
|
80
|
+
grid-template-columns: 1fr auto 1fr;
|
|
81
|
+
align-items: center;
|
|
82
|
+
|
|
83
|
+
/* Reserve the close button's width plus its right inset on both sides */
|
|
84
|
+
padding: 0
|
|
85
|
+
calc(
|
|
86
|
+
var(--dx-g-spacing-3xl) + var(--dx-g-spacing-xs) +
|
|
87
|
+
var(--doc-banner-padding-right)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.container_centered .icon {
|
|
92
|
+
grid-column: 1;
|
|
93
|
+
justify-self: end;
|
|
94
|
+
margin-top: 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.container_centered .main {
|
|
98
|
+
grid-column: 2;
|
|
99
|
+
align-items: center;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.container_centered .message {
|
|
104
|
+
text-align: center;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.container_centered .close {
|
|
108
|
+
position: absolute;
|
|
109
|
+
top: 50%;
|
|
110
|
+
right: var(--doc-banner-padding-right);
|
|
111
|
+
margin-left: 0;
|
|
112
|
+
transform: translateY(-50%);
|
|
113
|
+
}
|
|
114
|
+
|
|
76
115
|
@media (max-width: 1279px) {
|
|
77
116
|
:host {
|
|
78
117
|
--doc-banner-padding-left: var(--dx-g-spacing-xl);
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template lwc:if={showBanner}>
|
|
3
|
-
<div class=
|
|
3
|
+
<div class={containerClass} part="container">
|
|
4
4
|
<dx-icon
|
|
5
5
|
class="icon"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
sprite={iconSprite}
|
|
7
|
+
symbol={iconSymbol}
|
|
8
|
+
size={iconSize}
|
|
9
|
+
color={iconColor}
|
|
9
10
|
part="icon"
|
|
10
11
|
></dx-icon>
|
|
11
12
|
<div class="main">
|
|
12
13
|
<div class="message" part="message" lwc:dom="manual"></div>
|
|
13
|
-
<div class="actions" part="actions">
|
|
14
|
+
<div lwc:if={hasActions} class="actions" part="actions">
|
|
14
15
|
<template lwc:if={hasPrimaryButton}>
|
|
15
16
|
<dx-button
|
|
16
17
|
href={buttonHref}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { LightningElement, api } from "lwc";
|
|
2
|
+
import { IconSize, IconSprite, IconSymbol } from "typings/custom";
|
|
3
|
+
import { normalizeBoolean } from "dxUtils/normalizers";
|
|
2
4
|
|
|
3
5
|
const BANNER_STORAGE_PREFIX = "doc-banner-";
|
|
4
6
|
|
|
@@ -13,6 +15,24 @@ export default class Banner extends LightningElement {
|
|
|
13
15
|
|
|
14
16
|
@api dismissStorageKey = "";
|
|
15
17
|
|
|
18
|
+
@api
|
|
19
|
+
get centered(): boolean {
|
|
20
|
+
return this._centered;
|
|
21
|
+
}
|
|
22
|
+
set centered(value: boolean | string) {
|
|
23
|
+
this._centered = normalizeBoolean(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@api iconSprite: IconSprite = "utility";
|
|
27
|
+
|
|
28
|
+
@api iconSymbol: IconSymbol = "info";
|
|
29
|
+
|
|
30
|
+
@api iconColor = "gray-50";
|
|
31
|
+
|
|
32
|
+
@api iconSize: IconSize = "large";
|
|
33
|
+
|
|
34
|
+
private _centered = false;
|
|
35
|
+
|
|
16
36
|
private _dismissed = false;
|
|
17
37
|
|
|
18
38
|
connectedCallback() {
|
|
@@ -33,10 +53,18 @@ export default class Banner extends LightningElement {
|
|
|
33
53
|
}
|
|
34
54
|
}
|
|
35
55
|
|
|
56
|
+
get containerClass(): string {
|
|
57
|
+
return this._centered ? "container container_centered" : "container";
|
|
58
|
+
}
|
|
59
|
+
|
|
36
60
|
get showBanner(): boolean {
|
|
37
61
|
return !this._dismissed;
|
|
38
62
|
}
|
|
39
63
|
|
|
64
|
+
get hasActions(): boolean {
|
|
65
|
+
return this.hasPrimaryButton || this.hasSecondaryAction;
|
|
66
|
+
}
|
|
67
|
+
|
|
40
68
|
get hasPrimaryButton(): boolean {
|
|
41
69
|
return !!(this.buttonLabel && this.buttonHref);
|
|
42
70
|
}
|
|
@@ -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
|
+
* Attempts the one-shot hash scroll and marks it complete only if scrolling succeeds,
|
|
505
|
+
* keeping both trigger points consistent.
|
|
506
|
+
*/
|
|
507
|
+
// eslint-disable-next-line no-undef
|
|
508
|
+
protected tryScrollToHash(headingElements: NodeListOf<Element>): void {
|
|
509
|
+
if (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,13 +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 type { DocPhaseInfo, OptionWithLink } from "typings/custom";
|
|
11
|
+
import type { OptionWithLink } from "typings/custom";
|
|
14
12
|
|
|
15
13
|
declare global {
|
|
16
14
|
interface Window {
|
|
@@ -35,18 +33,8 @@ type ReferenceItem = {
|
|
|
35
33
|
topic?: ReferenceTopic;
|
|
36
34
|
};
|
|
37
35
|
|
|
38
|
-
/** A selectable spec version. Mirrors `ReferenceVersion` in `doc/amfReference`. */
|
|
39
|
-
type ReferenceVersion = {
|
|
40
|
-
id: string;
|
|
41
|
-
label: string;
|
|
42
|
-
deprecated?: boolean;
|
|
43
|
-
selected?: boolean;
|
|
44
|
-
link: { href: string };
|
|
45
|
-
};
|
|
46
|
-
|
|
47
36
|
type ReferenceConfig = {
|
|
48
37
|
refList: ReferenceItem[];
|
|
49
|
-
versions?: ReferenceVersion[];
|
|
50
38
|
};
|
|
51
39
|
|
|
52
40
|
const SCROLL_THROTTLE_DELAY = 50;
|
|
@@ -54,7 +42,6 @@ const ELEMENT_TIMEOUT = 10000;
|
|
|
54
42
|
const ELEMENT_CHECK_INTERVAL = 100;
|
|
55
43
|
const DEFAULT_PROJECT_TITLE = "All Reference";
|
|
56
44
|
const BACK_TARGET_STORAGE_KEY = "redoc-back-target";
|
|
57
|
-
const SELECTED_VIEW_STORAGE_KEY = "redoc-selected-view";
|
|
58
45
|
const DOCS_PATH_SEGMENT = "docs";
|
|
59
46
|
const DEFAULT_LOCALE = "en-us";
|
|
60
47
|
|
|
@@ -165,40 +152,6 @@ export default class RedocReference extends LightningElement {
|
|
|
165
152
|
return refCount > 1;
|
|
166
153
|
}
|
|
167
154
|
|
|
168
|
-
get versions(): ReferenceVersion[] {
|
|
169
|
-
return this._referenceConfig?.versions ?? [];
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** The version flagged `selected`, else the first (the latest/GA version). */
|
|
173
|
-
get selectedVersion(): ReferenceVersion | null {
|
|
174
|
-
const versions = this.versions;
|
|
175
|
-
return versions.find((v) => v.selected) ?? versions[0] ?? null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** True when the selected version is the latest/GA one (index 0). */
|
|
179
|
-
get latestVersion(): boolean {
|
|
180
|
-
return this.versions.length
|
|
181
|
-
? this.selectedVersion?.id === this.versions[0].id
|
|
182
|
-
: false;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
get hasVersionPicker(): boolean {
|
|
186
|
-
return this.versions.length > 0;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** "Newer version available" banner info when on a non-latest version, else null. */
|
|
190
|
-
private get oldVersionInfo(): DocPhaseInfo | null {
|
|
191
|
-
if (this.versions.length > 1 && !this.latestVersion) {
|
|
192
|
-
return oldVersionDocInfo(this.versions[0].link.href);
|
|
193
|
-
}
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** A single version has nothing to choose, so it renders read-only. */
|
|
198
|
-
get isVersionReadOnly(): boolean {
|
|
199
|
-
return this.versions.length === 1;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
155
|
// Reads stored back target
|
|
203
156
|
private getBackTargetFromSession(): string | null {
|
|
204
157
|
return sessionStorage.getItem(BACK_TARGET_STORAGE_KEY);
|
|
@@ -249,9 +202,7 @@ export default class RedocReference extends LightningElement {
|
|
|
249
202
|
}
|
|
250
203
|
|
|
251
204
|
const referrer = this.getSameOriginReferrerHref();
|
|
252
|
-
return referrer &&
|
|
253
|
-
!this.isLocaleHref(new URL(referrer).pathname) &&
|
|
254
|
-
!this.isVersionHref(new URL(referrer).pathname)
|
|
205
|
+
return referrer && !this.isLocaleHref(new URL(referrer).pathname)
|
|
255
206
|
? referrer
|
|
256
207
|
: null;
|
|
257
208
|
}
|
|
@@ -290,10 +241,7 @@ export default class RedocReference extends LightningElement {
|
|
|
290
241
|
}
|
|
291
242
|
|
|
292
243
|
const referrerUrl = new URL(referrerHref);
|
|
293
|
-
if (
|
|
294
|
-
this.isLocaleHref(referrerUrl.pathname) ||
|
|
295
|
-
this.isVersionHref(referrerUrl.pathname)
|
|
296
|
-
) {
|
|
244
|
+
if (this.isLocaleHref(referrerUrl.pathname)) {
|
|
297
245
|
return;
|
|
298
246
|
}
|
|
299
247
|
|
|
@@ -334,19 +282,6 @@ export default class RedocReference extends LightningElement {
|
|
|
334
282
|
});
|
|
335
283
|
}
|
|
336
284
|
|
|
337
|
-
/**
|
|
338
|
-
* switching version on this same page.
|
|
339
|
-
*/
|
|
340
|
-
private isVersionHref(pathname: string): boolean {
|
|
341
|
-
return this.versions.some((version) => {
|
|
342
|
-
const href = version?.link?.href;
|
|
343
|
-
return (
|
|
344
|
-
!!href &&
|
|
345
|
-
new URL(href, window.location.origin).pathname === pathname
|
|
346
|
-
);
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
285
|
/** When origin is provided, pass it to the footer; otherwise use dx-footer's default. */
|
|
351
286
|
get effectiveFooterOrigin(): string {
|
|
352
287
|
return (
|
|
@@ -419,14 +354,6 @@ export default class RedocReference extends LightningElement {
|
|
|
419
354
|
return parseInt(value, 10) || 0;
|
|
420
355
|
}
|
|
421
356
|
|
|
422
|
-
/** Redoc's LNB background color, themeable via `--doc-c-redoc-sidebar-bg`. */
|
|
423
|
-
private get sidebarBackgroundColor(): string {
|
|
424
|
-
const value = getComputedStyle(this.template.host).getPropertyValue(
|
|
425
|
-
"--doc-c-redoc-sidebar-bg"
|
|
426
|
-
);
|
|
427
|
-
return value.trim() || "white";
|
|
428
|
-
}
|
|
429
|
-
|
|
430
357
|
/*
|
|
431
358
|
** Since we could not use --dx-g-global-header-height as getPropertyValue returns a calc expression,
|
|
432
359
|
** we are using the respective CSS variables to calculate the height.
|
|
@@ -542,14 +469,7 @@ export default class RedocReference extends LightningElement {
|
|
|
542
469
|
specUrl,
|
|
543
470
|
{
|
|
544
471
|
// Dynamic scroll offset to account for headers
|
|
545
|
-
scrollYOffset: this.calculateScrollYOffset
|
|
546
|
-
// Redoc's own styled background outranks injected
|
|
547
|
-
// CSS, so the LNB background must be set via theme.
|
|
548
|
-
theme: {
|
|
549
|
-
sidebar: {
|
|
550
|
-
backgroundColor: this.sidebarBackgroundColor
|
|
551
|
-
}
|
|
552
|
-
}
|
|
472
|
+
scrollYOffset: this.calculateScrollYOffset
|
|
553
473
|
},
|
|
554
474
|
redocContainer,
|
|
555
475
|
(error: any) => {
|
|
@@ -595,7 +515,10 @@ export default class RedocReference extends LightningElement {
|
|
|
595
515
|
const apiContentDiv = await this.waitForApiContent(redocContainer);
|
|
596
516
|
apiContentDiv.setAttribute("lwc:dom", "manual");
|
|
597
517
|
|
|
598
|
-
this.
|
|
518
|
+
const docPhaseInfo = this.getDocPhaseInfo();
|
|
519
|
+
if (docPhaseInfo) {
|
|
520
|
+
this.insertDocPhase(apiContentDiv, docPhaseInfo);
|
|
521
|
+
}
|
|
599
522
|
|
|
600
523
|
this.appendFooterItems(apiContentDiv);
|
|
601
524
|
|
|
@@ -604,22 +527,10 @@ export default class RedocReference extends LightningElement {
|
|
|
604
527
|
|
|
605
528
|
// Wait for footer to be rendered before updating styles
|
|
606
529
|
requestAnimationFrame(() => {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
// after all layout mutations (footer, header) are
|
|
612
|
-
// complete, so the scroll lands on the correct element.
|
|
613
|
-
this.restoreSelectedView();
|
|
614
|
-
|
|
615
|
-
// Fix initial hash scroll after doc phase insertion
|
|
616
|
-
this.handleInitialHashScrollFix();
|
|
617
|
-
} catch (error) {
|
|
618
|
-
this.showErrorUI(
|
|
619
|
-
"Failed to integrate custom components:",
|
|
620
|
-
error
|
|
621
|
-
);
|
|
622
|
-
}
|
|
530
|
+
this.updateRedocThirdColumnStyle(redocContainer);
|
|
531
|
+
|
|
532
|
+
// Fix initial hash scroll after doc phase insertion
|
|
533
|
+
this.handleInitialHashScrollFix();
|
|
623
534
|
});
|
|
624
535
|
} catch (error) {
|
|
625
536
|
this.showErrorUI("Failed to integrate custom components:", error);
|
|
@@ -649,53 +560,6 @@ export default class RedocReference extends LightningElement {
|
|
|
649
560
|
}
|
|
650
561
|
}
|
|
651
562
|
|
|
652
|
-
/** Builds the version picker DOM by reusing `doc-version-picker`. */
|
|
653
|
-
private buildVersionPickerDom(): HTMLElement {
|
|
654
|
-
const wrapper = document.createElement("div");
|
|
655
|
-
wrapper.className = "redoc-version-picker";
|
|
656
|
-
|
|
657
|
-
const picker = createElement("doc-version-picker", {
|
|
658
|
-
is: VersionPicker
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
Object.assign(picker, {
|
|
662
|
-
versions: this.versions,
|
|
663
|
-
selectedVersion: this.selectedVersion,
|
|
664
|
-
latestVersion: this.latestVersion,
|
|
665
|
-
readOnly: this.isVersionReadOnly
|
|
666
|
-
});
|
|
667
|
-
picker.addEventListener("change", this.onVersionChange);
|
|
668
|
-
wrapper.appendChild(picker);
|
|
669
|
-
|
|
670
|
-
return wrapper;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/** Stashes the current view (URL hash) before the version link navigates. */
|
|
674
|
-
private onVersionChange = (): void => {
|
|
675
|
-
sessionStorage.setItem(
|
|
676
|
-
SELECTED_VIEW_STORAGE_KEY,
|
|
677
|
-
window.location.hash || ""
|
|
678
|
-
);
|
|
679
|
-
};
|
|
680
|
-
|
|
681
|
-
/**
|
|
682
|
-
* Re-applies the view stashed before a version switch, if its anchor exists
|
|
683
|
-
* in the new spec; otherwise a no-op (lands on the spec root).
|
|
684
|
-
*/
|
|
685
|
-
private restoreSelectedView(): void {
|
|
686
|
-
const storedHash = sessionStorage.getItem(SELECTED_VIEW_STORAGE_KEY);
|
|
687
|
-
sessionStorage.removeItem(SELECTED_VIEW_STORAGE_KEY);
|
|
688
|
-
|
|
689
|
-
if (!storedHash || window.location.hash) {
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
const targetId = storedHash.replace(/^#/, "");
|
|
694
|
-
if (targetId && document.getElementById(targetId)) {
|
|
695
|
-
window.location.hash = storedHash;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
563
|
/**
|
|
700
564
|
* Builds the locale picker DOM by reusing `dx-sidebar-footer-nav`
|
|
701
565
|
*/
|
|
@@ -717,17 +581,12 @@ export default class RedocReference extends LightningElement {
|
|
|
717
581
|
}
|
|
718
582
|
|
|
719
583
|
/**
|
|
720
|
-
* Builds
|
|
721
|
-
* versions are available, the version picker as a sibling row. Layout/gaps
|
|
722
|
-
* are styled by the developer-website's redoc CSS.
|
|
584
|
+
* Builds a fresh project-title/spec-title header DOM node.
|
|
723
585
|
*/
|
|
724
586
|
private buildProjectHeaderDom(): HTMLElement {
|
|
725
587
|
const wrapper = document.createElement("div");
|
|
726
588
|
wrapper.className = "redoc-project-header";
|
|
727
589
|
|
|
728
|
-
const main = document.createElement("div");
|
|
729
|
-
main.className = "redoc-project-header-main";
|
|
730
|
-
|
|
731
590
|
if (this.projectTitle) {
|
|
732
591
|
const backLink = document.createElement("a");
|
|
733
592
|
backLink.className = "redoc-project-back";
|
|
@@ -748,20 +607,14 @@ export default class RedocReference extends LightningElement {
|
|
|
748
607
|
|
|
749
608
|
backLink.appendChild(icon);
|
|
750
609
|
backLink.appendChild(label);
|
|
751
|
-
|
|
610
|
+
wrapper.appendChild(backLink);
|
|
752
611
|
}
|
|
753
612
|
|
|
754
613
|
if (this.specTitle) {
|
|
755
614
|
const specEl = document.createElement("h2");
|
|
756
615
|
specEl.className = "redoc-spec-title dx-text-display-7";
|
|
757
616
|
specEl.textContent = this.specTitle;
|
|
758
|
-
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
wrapper.appendChild(main);
|
|
762
|
-
|
|
763
|
-
if (this.hasVersionPicker) {
|
|
764
|
-
wrapper.appendChild(this.buildVersionPickerDom());
|
|
617
|
+
wrapper.appendChild(specEl);
|
|
765
618
|
}
|
|
766
619
|
|
|
767
620
|
return wrapper;
|
|
@@ -786,40 +639,15 @@ export default class RedocReference extends LightningElement {
|
|
|
786
639
|
return container.querySelector<HTMLElement>(".api-content")!;
|
|
787
640
|
}
|
|
788
641
|
|
|
789
|
-
//
|
|
790
|
-
|
|
791
|
-
private insertStatusItems(container: HTMLElement): void {
|
|
792
|
-
const docPhaseInfo = this.getDocPhaseInfo();
|
|
793
|
-
const oldVersionInfo = this.oldVersionInfo;
|
|
794
|
-
if (!docPhaseInfo && !oldVersionInfo) {
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
797
|
-
|
|
642
|
+
// Creates and inserts doc phase component at container start
|
|
643
|
+
private insertDocPhase(container: HTMLElement, docPhaseInfo: string): void {
|
|
798
644
|
const wrapper = document.createElement("div");
|
|
799
645
|
wrapper.className = "doc-phase-wrapper";
|
|
800
646
|
container.insertBefore(wrapper, container.firstChild);
|
|
801
647
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
});
|
|
806
|
-
Object.assign(docPhaseElement, { docPhaseInfo });
|
|
807
|
-
wrapper.appendChild(docPhaseElement);
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
if (oldVersionInfo) {
|
|
811
|
-
const versionBanner = createElement("doc-phase", { is: DocPhase });
|
|
812
|
-
Object.assign(versionBanner, {
|
|
813
|
-
docPhaseInfo: oldVersionInfo,
|
|
814
|
-
dismissible: true,
|
|
815
|
-
iconName: "warning"
|
|
816
|
-
});
|
|
817
|
-
versionBanner.addEventListener("dismissphase", () => {
|
|
818
|
-
versionBanner.remove();
|
|
819
|
-
this.updateSidebarPosition();
|
|
820
|
-
});
|
|
821
|
-
wrapper.appendChild(versionBanner);
|
|
822
|
-
}
|
|
648
|
+
const docPhaseElement = createElement("doc-phase", { is: DocPhase });
|
|
649
|
+
Object.assign(docPhaseElement, { docPhaseInfo });
|
|
650
|
+
wrapper.appendChild(docPhaseElement);
|
|
823
651
|
}
|
|
824
652
|
|
|
825
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
|
|