@salesforcedevs/docs-components 1.34.4-topic-alpha.5 → 1.34.5-tlv-alpha-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 +29 -28
- 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/breadcrumbs/breadcrumbs.css +0 -1
- package/src/modules/doc/contentActionToolbar/contentActionToolbar.ts +12 -12
- package/src/modules/doc/header/header.ts +19 -0
- package/src/modules/doc/nav/nav.css +1 -1
- package/src/modules/doc/redocReference/redocReference.ts +105 -1
- package/src/modules/doc/unifiedContentLayout/unifiedContentLayout.ts +8 -39
- package/src/modules/doc/versionPicker/versionPicker.css +86 -29
- package/src/modules/doc/versionPicker/versionPicker.html +1 -1
package/package.json
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
2
|
+
"name": "@salesforcedevs/docs-components",
|
|
3
|
+
"version": "1.34.5-tlv-alpha-1",
|
|
4
|
+
"description": "Docs Lightning web components for DSC",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": "22.x"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@api-components/amf-helper-mixin": "4.5.29",
|
|
15
|
+
"classnames": "2.5.1",
|
|
16
|
+
"dompurify": "3.2.4",
|
|
17
|
+
"kagekiri": "1.4.2",
|
|
18
|
+
"lodash.orderby": "4.6.0",
|
|
19
|
+
"lodash.uniqby": "4.7.0",
|
|
20
|
+
"query-string": "7.1.3",
|
|
21
|
+
"sentence-case": "3.0.4"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/classnames": "2.3.1",
|
|
25
|
+
"@types/lodash.orderby": "4.6.9",
|
|
26
|
+
"@types/lodash.uniqby": "4.7.9"
|
|
27
|
+
},
|
|
28
|
+
"gitHead": "4629fdd9ca18a13480044ad43515b91945d16aad",
|
|
29
|
+
"stableVersion": "1.34.4"
|
|
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
|
}
|
|
@@ -127,26 +127,26 @@ export default class ContentActionToolbar extends LightningElement {
|
|
|
127
127
|
{
|
|
128
128
|
id: ACTION_COPY_MARKDOWN,
|
|
129
129
|
label: COPY_MARKDOWN_LABEL,
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
iconSprite: "utility",
|
|
131
|
+
iconSymbol: "copy"
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
id: ACTION_VIEW_MARKDOWN,
|
|
135
135
|
label: VIEW_MARKDOWN_LABEL,
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
iconSprite: "utility",
|
|
137
|
+
iconSymbol: "new_window"
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
id: ACTION_COPY_MARKDOWN_URL,
|
|
141
141
|
label: COPY_MARKDOWN_URL_LABEL,
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
iconSprite: "utility",
|
|
143
|
+
iconSymbol: "link"
|
|
144
144
|
},
|
|
145
145
|
...AI_ASSISTANTS.map(({ action, label }) => ({
|
|
146
146
|
id: action,
|
|
147
147
|
label,
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
iconSprite: "utility",
|
|
149
|
+
iconSymbol: "new_window"
|
|
150
150
|
}))
|
|
151
151
|
]
|
|
152
152
|
});
|
|
@@ -159,14 +159,14 @@ export default class ContentActionToolbar extends LightningElement {
|
|
|
159
159
|
{
|
|
160
160
|
id: ACTION_VIEW_LLMS,
|
|
161
161
|
label: VIEW_LLMS_LABEL,
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
iconSprite: "utility",
|
|
163
|
+
iconSymbol: "new_window"
|
|
164
164
|
},
|
|
165
165
|
{
|
|
166
166
|
id: ACTION_COPY_LLMS_URL,
|
|
167
167
|
label: COPY_LLMS_URL_LABEL,
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
iconSprite: "utility",
|
|
169
|
+
iconSymbol: "link"
|
|
170
170
|
}
|
|
171
171
|
]
|
|
172
172
|
});
|
|
@@ -5,6 +5,7 @@ import { HeaderBase } from "dxBaseElements/headerBase";
|
|
|
5
5
|
import { toJson, normalizeBoolean } from "dxUtils/normalizers";
|
|
6
6
|
import { track } from "dxUtils/analytics";
|
|
7
7
|
|
|
8
|
+
const TABLET_MATCH = "980px";
|
|
8
9
|
const MOBILE_MATCH = "768px";
|
|
9
10
|
|
|
10
11
|
const isStorybook = () => {
|
|
@@ -46,6 +47,8 @@ export default class Header extends HeaderBase {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
private _scopedNavItems!: OptionWithNested[];
|
|
50
|
+
private tablet = false;
|
|
51
|
+
private tabletMatchMedia!: MediaQueryList;
|
|
49
52
|
private shouldRender: boolean = false;
|
|
50
53
|
private showDocDivider: boolean = false;
|
|
51
54
|
private _devCenter!: DevCenterConfig;
|
|
@@ -80,6 +83,11 @@ export default class Header extends HeaderBase {
|
|
|
80
83
|
|
|
81
84
|
connectedCallback(): void {
|
|
82
85
|
super.connectedCallback();
|
|
86
|
+
this.tabletMatchMedia = window.matchMedia(
|
|
87
|
+
`(max-width: ${TABLET_MATCH})`
|
|
88
|
+
);
|
|
89
|
+
this.onTabletChange(this.tabletMatchMedia);
|
|
90
|
+
this.tabletMatchMedia.addEventListener("change", this.onTabletChange);
|
|
83
91
|
|
|
84
92
|
if (
|
|
85
93
|
(!this.shouldHideHeader && this.isDocsPath) ||
|
|
@@ -98,12 +106,23 @@ export default class Header extends HeaderBase {
|
|
|
98
106
|
}
|
|
99
107
|
}
|
|
100
108
|
|
|
109
|
+
disconnectedCallback(): void {
|
|
110
|
+
super.disconnectedCallback();
|
|
111
|
+
this.tabletMatchMedia.removeEventListener(
|
|
112
|
+
"change",
|
|
113
|
+
this.onTabletChange
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
101
117
|
renderedCallback(): void {
|
|
102
118
|
if (this.hideDocHeader) {
|
|
103
119
|
this.shouldRender = !this.shouldHideHeader && this.isDocsPath;
|
|
104
120
|
}
|
|
105
121
|
}
|
|
106
122
|
|
|
123
|
+
private onTabletChange = (e: MediaQueryListEvent | MediaQueryList) =>
|
|
124
|
+
(this.tablet = e.matches);
|
|
125
|
+
|
|
107
126
|
protected additionalClasses(): string {
|
|
108
127
|
return cx(
|
|
109
128
|
this.brand && "has-brand",
|
|
@@ -10,6 +10,15 @@ import { throttle } from "throttle-debounce";
|
|
|
10
10
|
import { pollUntil } from "dxUtils/async";
|
|
11
11
|
import { toJson } from "dxUtils/normalizers";
|
|
12
12
|
import { oldVersionDocInfo } from "docUtils/utils";
|
|
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";
|
|
13
22
|
import type { DocPhaseInfo, OptionWithLink } from "typings/custom";
|
|
14
23
|
|
|
15
24
|
declare global {
|
|
@@ -47,6 +56,8 @@ type ReferenceVersion = {
|
|
|
47
56
|
type ReferenceConfig = {
|
|
48
57
|
refList: ReferenceItem[];
|
|
49
58
|
versions?: ReferenceVersion[];
|
|
59
|
+
/** Sidebar topic name. Sidebar stores the selected version under this key. */
|
|
60
|
+
versionTopicKey?: string;
|
|
50
61
|
};
|
|
51
62
|
|
|
52
63
|
const SCROLL_THROTTLE_DELAY = 50;
|
|
@@ -129,6 +140,29 @@ export default class RedocReference extends LightningElement {
|
|
|
129
140
|
*/
|
|
130
141
|
@api headerHref: string | null = null;
|
|
131
142
|
|
|
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
|
+
|
|
132
166
|
@api
|
|
133
167
|
get contentType(): string {
|
|
134
168
|
return this._contentType;
|
|
@@ -233,13 +267,64 @@ export default class RedocReference extends LightningElement {
|
|
|
233
267
|
private onBackClick = (event: Event): void => {
|
|
234
268
|
event.preventDefault();
|
|
235
269
|
|
|
236
|
-
const target = this.
|
|
270
|
+
const target = this.getVersionedBackTarget() ?? this.headerHref;
|
|
237
271
|
|
|
238
272
|
if (target) {
|
|
239
273
|
window.location.assign(target);
|
|
240
274
|
}
|
|
241
275
|
};
|
|
242
276
|
|
|
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
|
+
|
|
243
328
|
private getTranslatedBackTarget(): string | null {
|
|
244
329
|
const stored = this.getBackTargetFromSession();
|
|
245
330
|
if (stored) {
|
|
@@ -362,6 +447,13 @@ export default class RedocReference extends LightningElement {
|
|
|
362
447
|
// Track the back target, preserving it across locale switches
|
|
363
448
|
this.trackBackTarget();
|
|
364
449
|
|
|
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
|
+
|
|
365
457
|
window.addEventListener("scroll", this.handleScrollAndResize);
|
|
366
458
|
window.addEventListener("resize", this.handleScrollAndResize);
|
|
367
459
|
}
|
|
@@ -670,6 +762,18 @@ export default class RedocReference extends LightningElement {
|
|
|
670
762
|
return wrapper;
|
|
671
763
|
}
|
|
672
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
|
+
|
|
673
777
|
/** Stashes the current view (URL hash) before the version link navigates. */
|
|
674
778
|
private onVersionChange = (): void => {
|
|
675
779
|
sessionStorage.setItem(
|
|
@@ -16,36 +16,13 @@ import type { DocPhaseInfo, OptionWithLink, TreeNode } from "typings/custom";
|
|
|
16
16
|
*/
|
|
17
17
|
const TOPIC_TYPE_SPEC = "spec";
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
* Walks down the first-child chain and returns the first markdown content
|
|
21
|
-
* page found, descending through nested content-less parents. Returns
|
|
22
|
-
* undefined if the chain breaks on a spec or external link before reaching a
|
|
23
|
-
* content page.
|
|
24
|
-
*/
|
|
25
|
-
function firstChildContentPage(node: TreeNode): TreeNode | undefined {
|
|
26
|
-
const firstChild = node.children?.[0];
|
|
27
|
-
if (!firstChild) {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const isSpec =
|
|
32
|
-
(firstChild as { topicType?: string }).topicType === TOPIC_TYPE_SPEC;
|
|
33
|
-
const isExternal = firstChild.link?.target === "_blank";
|
|
34
|
-
if (isSpec || isExternal) {
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (firstChild.link?.href) {
|
|
39
|
-
return firstChild;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return firstChildContentPage(firstChild);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
19
|
/**
|
|
46
20
|
* Translates per-topic `topicType` into the generic `showForwardArrow` flag
|
|
47
|
-
* that `dx-tree-tile` reads (spec topics get the forward arrow icon for Redoc)
|
|
48
|
-
*
|
|
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.
|
|
49
26
|
*/
|
|
50
27
|
function decorateTopicsWithForwardArrow(
|
|
51
28
|
topics:
|
|
@@ -67,18 +44,10 @@ function decorateTopicsWithForwardArrow(
|
|
|
67
44
|
);
|
|
68
45
|
}
|
|
69
46
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const isContentlessParent =
|
|
74
|
-
!!decorated.children?.length && !decorated.link?.href;
|
|
75
|
-
|
|
76
|
-
if (isContentlessParent) {
|
|
47
|
+
// Flag a parent with no page of its own. Leave its link empty so the
|
|
48
|
+
// child's URL stays indexed to the child.
|
|
49
|
+
if (!!decorated.children?.length && !decorated.link?.href) {
|
|
77
50
|
decorated.redirectToChild = true;
|
|
78
|
-
const firstChild = firstChildContentPage(topic);
|
|
79
|
-
if (firstChild?.link) {
|
|
80
|
-
decorated.link = { ...firstChild.link };
|
|
81
|
-
}
|
|
82
51
|
}
|
|
83
52
|
|
|
84
53
|
return decorated;
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
@import "dxHelpers/reset";
|
|
2
2
|
|
|
3
|
-
/*
|
|
4
|
-
menu stops at --doc-version-picker-width, which the sidebar sets. */
|
|
3
|
+
/* NOTE: doc-version-picker-width width variable is used by both dx-button and dx-dropdown to maintain a consistent width. */
|
|
5
4
|
:host {
|
|
6
|
-
|
|
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;
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
--dx-c-dropdown-option-
|
|
10
|
+
--dx-c-dropdown-option-font-weight: normal;
|
|
11
|
+
--dx-c-dropdown-option-font-size: var(--dx-g-text-base);
|
|
12
|
+
--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;
|
|
10
17
|
--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);
|
|
11
20
|
}
|
|
12
21
|
|
|
13
22
|
/* Open menus must stack above later sibling pickers in the sidebar. */
|
|
14
23
|
:host(:has([aria-expanded="true"])) {
|
|
15
|
-
position: relative;
|
|
16
24
|
z-index: 2;
|
|
17
25
|
}
|
|
18
26
|
|
|
@@ -48,30 +56,15 @@
|
|
|
48
56
|
.version-picker-button {
|
|
49
57
|
display: flex;
|
|
50
58
|
width: 100%;
|
|
59
|
+
overflow: visible;
|
|
51
60
|
|
|
52
61
|
--dx-c-button-horizontal-spacing: var(--dx-g-spacing-sm);
|
|
53
|
-
--dx-
|
|
54
|
-
|
|
55
|
-
/* A real 1px border at rest. Hover and the open menu turn it dark blue and
|
|
56
|
-
add a 1px inset shadow, so the edge looks 2px without resizing. */
|
|
57
|
-
--dx-c-button-custom-color: var(--dx-g-gray-10);
|
|
58
|
-
--dx-c-button-custom-color-hover: var(--dx-g-gray-10);
|
|
59
|
-
--dx-c-button-custom-background: white;
|
|
60
|
-
--dx-c-button-custom-background-hover: var(--dx-g-blue-vibrant-95);
|
|
61
|
-
--dx-c-button-custom-border: var(--dx-g-gray-50);
|
|
62
|
-
--dx-c-button-custom-border-hover: var(--dx-g-blue-vibrant-20);
|
|
63
|
-
--dx-c-button-custom-border-shadow-hover: inset 0 0 0 1px
|
|
64
|
-
var(--dx-g-blue-vibrant-20);
|
|
65
|
-
|
|
66
|
-
/* Keyboard focus keeps the 2px edge inside the white band. */
|
|
67
|
-
--dx-c-button-focus-shadow: inset 0 0 0 1px var(--dx-g-blue-vibrant-20),
|
|
68
|
-
0 0 0 2px white;
|
|
62
|
+
--dx-g-button-icon-color: var(--dx-g-gray-50);
|
|
69
63
|
}
|
|
70
64
|
|
|
71
65
|
.version-picker-button:hover,
|
|
72
|
-
.version-picker-button:focus-within,
|
|
73
66
|
.version-picker-button[aria-expanded="true"] {
|
|
74
|
-
--dx-
|
|
67
|
+
--dx-g-button-icon-color: var(--dx-g-blue-vibrant-20);
|
|
75
68
|
}
|
|
76
69
|
|
|
77
70
|
/**
|
|
@@ -83,6 +76,57 @@ dx-button::part(content) {
|
|
|
83
76
|
overflow: hidden;
|
|
84
77
|
}
|
|
85
78
|
|
|
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
|
+
|
|
86
130
|
.selected-version {
|
|
87
131
|
display: flex;
|
|
88
132
|
flex-direction: row;
|
|
@@ -106,11 +150,25 @@ dx-type-badge.not-latest-badge {
|
|
|
106
150
|
* Small "dot" variant (opt-in via `small`). Additive
|
|
107
151
|
* ------------------------------------------------------------------ */
|
|
108
152
|
|
|
109
|
-
/*
|
|
110
|
-
|
|
111
|
-
that width. The large menu always matches its trigger (width-mode="full"). */
|
|
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. */
|
|
112
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. */
|
|
113
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;
|
|
114
172
|
}
|
|
115
173
|
|
|
116
174
|
.version-picker-button-small {
|
|
@@ -135,8 +193,7 @@ dx-type-badge.not-latest-badge {
|
|
|
135
193
|
}
|
|
136
194
|
|
|
137
195
|
/* width:100% re-ties to the 104px-capped host (container is width:inherit,
|
|
138
|
-
which copies width but not max-width). Border
|
|
139
|
-
dx-button hooks on .version-picker-button. */
|
|
196
|
+
which copies width but not max-width). Border/bg/states are shared above. */
|
|
140
197
|
.version-picker-button-small::part(container) {
|
|
141
198
|
width: 100%;
|
|
142
199
|
height: var(--dx-g-spacing-lg);
|