@salesforcedevs/docs-components 1.34.0 → 1.34.1-doc-phase-redoc-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 CHANGED
@@ -1,29 +1,30 @@
1
1
  {
2
- "name": "@salesforcedevs/docs-components",
3
- "version": "1.34.0",
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": "db75cd19dd7a77fe4e09856439c4905c32e64d1f"
29
- }
2
+ "name": "@salesforcedevs/docs-components",
3
+ "version": "1.34.1-doc-phase-redoc-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.0"
30
+ }
@@ -4,11 +4,13 @@ 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";
7
8
  import SprigSurvey from "doc/sprigSurvey";
8
9
  import { throttle } from "throttle-debounce";
9
10
  import { pollUntil } from "dxUtils/async";
10
11
  import { toJson } from "dxUtils/normalizers";
11
- import type { OptionWithLink } from "typings/custom";
12
+ import { oldVersionDocInfo } from "docUtils/utils";
13
+ import type { DocPhaseInfo, OptionWithLink } from "typings/custom";
12
14
 
13
15
  declare global {
14
16
  interface Window {
@@ -33,8 +35,18 @@ type ReferenceItem = {
33
35
  topic?: ReferenceTopic;
34
36
  };
35
37
 
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
+
36
47
  type ReferenceConfig = {
37
48
  refList: ReferenceItem[];
49
+ versions?: ReferenceVersion[];
38
50
  };
39
51
 
40
52
  const SCROLL_THROTTLE_DELAY = 50;
@@ -42,6 +54,7 @@ const ELEMENT_TIMEOUT = 10000;
42
54
  const ELEMENT_CHECK_INTERVAL = 100;
43
55
  const DEFAULT_PROJECT_TITLE = "All Reference";
44
56
  const BACK_TARGET_STORAGE_KEY = "redoc-back-target";
57
+ const SELECTED_VIEW_STORAGE_KEY = "redoc-selected-view";
45
58
  const DOCS_PATH_SEGMENT = "docs";
46
59
  const DEFAULT_LOCALE = "en-us";
47
60
 
@@ -152,6 +165,40 @@ export default class RedocReference extends LightningElement {
152
165
  return refCount > 1;
153
166
  }
154
167
 
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
+
155
202
  // Reads stored back target
156
203
  private getBackTargetFromSession(): string | null {
157
204
  return sessionStorage.getItem(BACK_TARGET_STORAGE_KEY);
@@ -202,7 +249,9 @@ export default class RedocReference extends LightningElement {
202
249
  }
203
250
 
204
251
  const referrer = this.getSameOriginReferrerHref();
205
- return referrer && !this.isLocaleHref(new URL(referrer).pathname)
252
+ return referrer &&
253
+ !this.isLocaleHref(new URL(referrer).pathname) &&
254
+ !this.isVersionHref(new URL(referrer).pathname)
206
255
  ? referrer
207
256
  : null;
208
257
  }
@@ -241,7 +290,10 @@ export default class RedocReference extends LightningElement {
241
290
  }
242
291
 
243
292
  const referrerUrl = new URL(referrerHref);
244
- if (this.isLocaleHref(referrerUrl.pathname)) {
293
+ if (
294
+ this.isLocaleHref(referrerUrl.pathname) ||
295
+ this.isVersionHref(referrerUrl.pathname)
296
+ ) {
245
297
  return;
246
298
  }
247
299
 
@@ -282,6 +334,19 @@ export default class RedocReference extends LightningElement {
282
334
  });
283
335
  }
284
336
 
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
+
285
350
  /** When origin is provided, pass it to the footer; otherwise use dx-footer's default. */
286
351
  get effectiveFooterOrigin(): string {
287
352
  return (
@@ -469,7 +534,10 @@ export default class RedocReference extends LightningElement {
469
534
  specUrl,
470
535
  {
471
536
  // Dynamic scroll offset to account for headers
472
- scrollYOffset: this.calculateScrollYOffset
537
+ scrollYOffset: this.calculateScrollYOffset,
538
+ // White LNB via theme (Redoc's styled background
539
+ // outranks injected CSS).
540
+ theme: { sidebar: { backgroundColor: "white" } }
473
541
  },
474
542
  redocContainer,
475
543
  (error: any) => {
@@ -515,16 +583,16 @@ export default class RedocReference extends LightningElement {
515
583
  const apiContentDiv = await this.waitForApiContent(redocContainer);
516
584
  apiContentDiv.setAttribute("lwc:dom", "manual");
517
585
 
518
- const docPhaseInfo = this.getDocPhaseInfo();
519
- if (docPhaseInfo) {
520
- this.insertDocPhase(apiContentDiv, docPhaseInfo);
521
- }
586
+ this.insertStatusItems(apiContentDiv);
522
587
 
523
588
  this.appendFooterItems(apiContentDiv);
524
589
 
525
590
  // Inject the multi-spec project header into Redoc's left menu only.
526
591
  this.insertSidebarNav(redocContainer);
527
592
 
593
+ // Restore the view selected before a version switch, if any.
594
+ this.restoreSelectedView();
595
+
528
596
  // Wait for footer to be rendered before updating styles
529
597
  requestAnimationFrame(() => {
530
598
  this.updateRedocThirdColumnStyle(redocContainer);
@@ -560,6 +628,53 @@ export default class RedocReference extends LightningElement {
560
628
  }
561
629
  }
562
630
 
631
+ /** Builds the version picker DOM by reusing `doc-version-picker`. */
632
+ private buildVersionPickerDom(): HTMLElement {
633
+ const wrapper = document.createElement("div");
634
+ wrapper.className = "redoc-version-picker";
635
+
636
+ const picker = createElement("doc-version-picker", {
637
+ is: VersionPicker
638
+ });
639
+
640
+ Object.assign(picker, {
641
+ versions: this.versions,
642
+ selectedVersion: this.selectedVersion,
643
+ latestVersion: this.latestVersion,
644
+ readOnly: this.isVersionReadOnly
645
+ });
646
+ picker.addEventListener("change", this.onVersionChange);
647
+ wrapper.appendChild(picker);
648
+
649
+ return wrapper;
650
+ }
651
+
652
+ /** Stashes the current view (URL hash) before the version link navigates. */
653
+ private onVersionChange = (): void => {
654
+ sessionStorage.setItem(
655
+ SELECTED_VIEW_STORAGE_KEY,
656
+ window.location.hash || ""
657
+ );
658
+ };
659
+
660
+ /**
661
+ * Re-applies the view stashed before a version switch, if its anchor exists
662
+ * in the new spec; otherwise a no-op (lands on the spec root).
663
+ */
664
+ private restoreSelectedView(): void {
665
+ const storedHash = sessionStorage.getItem(SELECTED_VIEW_STORAGE_KEY);
666
+ sessionStorage.removeItem(SELECTED_VIEW_STORAGE_KEY);
667
+
668
+ if (!storedHash || window.location.hash) {
669
+ return;
670
+ }
671
+
672
+ const targetId = storedHash.replace(/^#/, "");
673
+ if (targetId && document.getElementById(targetId)) {
674
+ window.location.hash = storedHash;
675
+ }
676
+ }
677
+
563
678
  /**
564
679
  * Builds the locale picker DOM by reusing `dx-sidebar-footer-nav`
565
680
  */
@@ -581,12 +696,17 @@ export default class RedocReference extends LightningElement {
581
696
  }
582
697
 
583
698
  /**
584
- * Builds a fresh project-title/spec-title header DOM node.
699
+ * Builds the doc header: a title group (back link + spec title) and, when
700
+ * versions are available, the version picker as a sibling row. Layout/gaps
701
+ * are styled by the developer-website's redoc CSS.
585
702
  */
586
703
  private buildProjectHeaderDom(): HTMLElement {
587
704
  const wrapper = document.createElement("div");
588
705
  wrapper.className = "redoc-project-header";
589
706
 
707
+ const main = document.createElement("div");
708
+ main.className = "redoc-project-header-main";
709
+
590
710
  if (this.projectTitle) {
591
711
  const backLink = document.createElement("a");
592
712
  backLink.className = "redoc-project-back";
@@ -607,14 +727,20 @@ export default class RedocReference extends LightningElement {
607
727
 
608
728
  backLink.appendChild(icon);
609
729
  backLink.appendChild(label);
610
- wrapper.appendChild(backLink);
730
+ main.appendChild(backLink);
611
731
  }
612
732
 
613
733
  if (this.specTitle) {
614
734
  const specEl = document.createElement("h2");
615
735
  specEl.className = "redoc-spec-title dx-text-display-7";
616
736
  specEl.textContent = this.specTitle;
617
- wrapper.appendChild(specEl);
737
+ main.appendChild(specEl);
738
+ }
739
+
740
+ wrapper.appendChild(main);
741
+
742
+ if (this.hasVersionPicker) {
743
+ wrapper.appendChild(this.buildVersionPickerDom());
618
744
  }
619
745
 
620
746
  return wrapper;
@@ -639,15 +765,42 @@ export default class RedocReference extends LightningElement {
639
765
  return container.querySelector<HTMLElement>(".api-content")!;
640
766
  }
641
767
 
642
- // Creates and inserts doc phase component at container start
643
- private insertDocPhase(container: HTMLElement, docPhaseInfo: string): void {
768
+ // Inserts the doc phase and, on a non-latest version, a dismissible
769
+ // "newer version" banner into a shared doc-phase-wrapper at container start
770
+ private insertStatusItems(container: HTMLElement): void {
771
+ const items: HTMLElement[] = [];
772
+
773
+ const docPhaseInfo = this.getDocPhaseInfo();
774
+ if (docPhaseInfo) {
775
+ const docPhaseElement = createElement("doc-phase", {
776
+ is: DocPhase
777
+ });
778
+ Object.assign(docPhaseElement, { docPhaseInfo });
779
+ items.push(docPhaseElement);
780
+ }
781
+
782
+ if (this.oldVersionInfo) {
783
+ const versionBanner = createElement("doc-phase", { is: DocPhase });
784
+ Object.assign(versionBanner, {
785
+ docPhaseInfo: this.oldVersionInfo,
786
+ dismissible: true,
787
+ iconName: "warning"
788
+ });
789
+ versionBanner.addEventListener("dismissphase", () => {
790
+ versionBanner.remove();
791
+ this.updateSidebarPosition();
792
+ });
793
+ items.push(versionBanner);
794
+ }
795
+
796
+ if (!items.length) {
797
+ return;
798
+ }
799
+
644
800
  const wrapper = document.createElement("div");
645
801
  wrapper.className = "doc-phase-wrapper";
802
+ items.forEach((item) => wrapper.appendChild(item));
646
803
  container.insertBefore(wrapper, container.firstChild);
647
-
648
- const docPhaseElement = createElement("doc-phase", { is: DocPhase });
649
- Object.assign(docPhaseElement, { docPhaseInfo });
650
- wrapper.appendChild(docPhaseElement);
651
804
  }
652
805
 
653
806
  // Appends footer component to container
@@ -4,26 +4,44 @@
4
4
  :host {
5
5
  --dx-c-dropdown-option-font-weight: normal;
6
6
  --dx-c-dropdown-option-label-color: var(--dx-g-gray-10);
7
+ --dx-c-dropdown-option-padding: var(--dx-g-spacing-sm)
8
+ var(--dx-g-spacing-lg);
9
+ --dx-c-popover-padding: var(--dx-g-spacing-sm) 0;
7
10
  --popover-container-open-transform: translateY(4px);
8
11
  }
9
12
 
10
13
  .version-picker-container {
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);
14
+ /* Override --doc-version-picker-padding to change the inset (e.g. when the
15
+ picker sits inside an already-padded container). */
16
+ padding: var(
17
+ --doc-version-picker-padding,
18
+ 8px var(--dx-g-spacing-lg) 8px
19
+ var(--dx-g-global-header-padding-horizontal)
20
+ );
21
+
22
+ /* Override --doc-version-picker-divider (e.g. `none`) to restyle dividers. */
23
+ border-top: var(
24
+ --doc-version-picker-divider,
25
+ 1px solid var(--dx-g-gray-90)
26
+ );
27
+ border-bottom: var(
28
+ --doc-version-picker-divider,
29
+ 1px solid var(--dx-g-gray-90)
30
+ );
15
31
  }
16
32
 
17
33
  .version-picker-button {
18
34
  display: flex;
19
35
  width: var(--doc-version-picker-width, 296px);
36
+
37
+ --dx-c-button-horizontal-spacing: var(--dx-g-spacing-sm);
38
+ --dx-g-button-icon-color: var(--dx-g-gray-50);
20
39
  }
21
40
 
22
41
  .version-picker-button:hover,
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);
42
+ .version-picker-button:focus-within,
43
+ .version-picker-button[aria-expanded="true"] {
44
+ --dx-g-button-icon-color: var(--dx-g-blue-vibrant-20);
27
45
  }
28
46
 
29
47
  /**
@@ -35,6 +53,27 @@ dx-button::part(content) {
35
53
  overflow: hidden;
36
54
  }
37
55
 
56
+ /* The border is an inset box-shadow so thickening it on hover/focus doesn't
57
+ resize the small variant's fit-content box. */
58
+ .version-picker-button::part(container) {
59
+ background: white;
60
+ box-shadow: inset 0 0 0 1px var(--dx-g-gray-50);
61
+ color: var(--dx-g-gray-10);
62
+ }
63
+
64
+ .version-picker-button::part(container):hover {
65
+ background: var(--dx-g-blue-vibrant-95);
66
+ box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20);
67
+ }
68
+
69
+ /* aria-expanded keeps the focus ring while the menu is open. */
70
+ .version-picker-button:focus-within::part(container),
71
+ .version-picker-button[aria-expanded="true"]::part(container) {
72
+ background: var(--dx-g-blue-vibrant-95);
73
+ box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20), 0 0 0 2px white,
74
+ 0 0 0 4px var(--dx-g-blue-vibrant-60);
75
+ }
76
+
38
77
  .selected-version {
39
78
  display: flex;
40
79
  flex-direction: row;
@@ -49,16 +88,100 @@ dx-button::part(content) {
49
88
  white-space: nowrap;
50
89
  }
51
90
 
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);
55
-
91
+ dx-type-badge.latest-badge,
92
+ dx-type-badge.not-latest-badge {
56
93
  margin-left: var(--dx-g-spacing-sm);
57
94
  }
58
95
 
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);
96
+ /* ------------------------------------------------------------------ *
97
+ * Small "dot" variant (opt-in via `small`). Additive
98
+ * ------------------------------------------------------------------ */
62
99
 
63
- margin-left: var(--dx-g-spacing-sm);
100
+ /* Option padding is sm both axes: the small menu tracks the ~104px trigger, so
101
+ the default 24px horizontal would crowd the labels. */
102
+ .version-picker-dropdown-small {
103
+ --dx-c-dropdown-option-font-size: var(--dx-g-text-xs);
104
+ --dx-c-dropdown-option-padding: var(--dx-g-spacing-sm)
105
+ var(--dx-g-spacing-sm);
106
+ --dx-c-dropdown-option-border-radius: 0;
107
+ --dx-c-popover-border: none;
108
+ }
109
+
110
+ .version-picker-button-small {
111
+ width: fit-content;
112
+ max-width: 104px;
113
+
114
+ --dx-c-button-font-size: var(--dx-g-text-xs);
115
+ --dx-c-button-font-weight: var(--dx-g-font-normal);
116
+ --dx-c-button-line-height: var(--dx-g-spacing-lg);
117
+ --dx-c-button-icon-gap: var(--dx-g-spacing-2xs);
118
+ }
119
+
120
+ /* min-width: 0 on each flex ancestor lets the label truncate: a flex item's
121
+ default min-width: auto refuses to shrink below its content and overrides the
122
+ host's max-width, so without this the trigger overflows instead. */
123
+ .version-picker-button-small::part(content) {
124
+ display: flex;
125
+ flex: 1;
126
+ min-width: 0;
127
+ width: auto;
128
+ overflow: hidden;
129
+ }
130
+
131
+ /* width:100% re-ties to the 104px-capped host (container is width:inherit,
132
+ which copies width but not max-width). Border/bg/states are shared above. */
133
+ .version-picker-button-small::part(container) {
134
+ width: 100%;
135
+ height: var(--dx-g-spacing-lg);
136
+ padding: 0 var(--dx-g-spacing-xs);
137
+ }
138
+
139
+ .selected-version-small {
140
+ flex: 1;
141
+ min-width: 0;
142
+ }
143
+
144
+ .version-picker-dot {
145
+ flex: 0 0 auto;
146
+ width: var(--dx-g-spacing-sm);
147
+ height: var(--dx-g-spacing-sm);
148
+ margin-right: var(--dx-g-spacing-xs);
149
+ border-radius: 50%;
150
+ }
151
+
152
+ .version-picker-dot-latest {
153
+ background: var(--dx-g-green-vibrant-60);
154
+ }
155
+
156
+ .version-picker-dot-not-latest {
157
+ background: var(--dx-g-yellow-vibrant-80);
158
+ }
159
+
160
+ /* inline-block (not flex) so the label can truncate; line-height centers it. */
161
+ .version-picker-readonly {
162
+ box-sizing: border-box;
163
+ display: inline-block;
164
+ width: var(--doc-version-picker-width, 296px);
165
+ height: var(--dx-g-spacing-xl);
166
+ margin: 0;
167
+ padding: 0 var(--dx-g-spacing-sm);
168
+ border: 1px solid var(--dx-g-gray-80);
169
+ border-radius: var(--dx-g-spacing-xs);
170
+ overflow: hidden;
171
+ color: var(--dx-g-gray-10);
172
+ text-overflow: ellipsis;
173
+ white-space: nowrap;
174
+ font: var(--dx-g-font-normal) var(--dx-g-text-sm) / var(--dx-g-spacing-xl)
175
+ var(--dx-g-font-sans),
176
+ sans-serif;
177
+ }
178
+
179
+ .version-picker-readonly-small {
180
+ width: fit-content;
181
+ max-width: 104px;
182
+ height: var(--dx-g-spacing-lg);
183
+ padding: 0 var(--dx-g-spacing-xs);
184
+ font: var(--dx-g-font-normal) var(--dx-g-text-xs) / var(--dx-g-spacing-lg)
185
+ var(--dx-g-font-sans),
186
+ sans-serif;
64
187
  }
@@ -1,37 +1,70 @@
1
1
  <template>
2
2
  <div lwc:if={showVersionPicker} class="version-picker-container">
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
+
3
12
  <dx-dropdown
13
+ lwc:else
14
+ class={dropdownClass}
4
15
  options={versions}
5
16
  analytics-event="custEv_docVersionSelect"
6
17
  analytics-payload={analyticsPayload}
7
18
  value={selectedVersion.id}
8
- width="var(--doc-version-picker-width)"
19
+ full-width="true"
9
20
  onchange={onVersionChange}
10
21
  >
11
22
  <dx-button
12
- class="version-picker-button"
23
+ class={triggerClass}
13
24
  variant="tertiary"
14
25
  size="small"
26
+ font="sans"
15
27
  icon-symbol="chevrondown"
16
- icon-size="medium"
28
+ icon-size={triggerIconSize}
29
+ aria-label={triggerAriaLabel}
17
30
  >
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>
31
+ <div class={selectedVersionClass}>
32
+ <!-- Small: leading colored dot -->
33
+ <template lwc:if={small}>
34
+ <template lwc:if={showLatestTag}>
35
+ <span class={dotClass} aria-hidden="true"></span>
36
+ </template>
37
+ <p
38
+ class="selected-version-label"
39
+ title={selectedVersion.label}
40
+ >
41
+ {selectedVersion.label}
42
+ </p>
43
+ </template>
44
+ <!-- Default: trailing Latest/Not-Latest badge -->
45
+ <template lwc:else>
46
+ <p
47
+ class="selected-version-label"
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>
35
68
  </template>
36
69
  </div>
37
70
  </dx-button>
@@ -1,4 +1,5 @@
1
1
  import { LightningElement, api, track } from "lwc";
2
+ import cx from "classnames";
2
3
 
3
4
  import { AnalyticsPayload, OptionWithNested } from "typings/custom";
4
5
 
@@ -12,6 +13,8 @@ export default class VersionPicker extends LightningElement {
12
13
  private _selectedVersion?: OptionWithNested;
13
14
  private _latestVersion: boolean = false;
14
15
  private _hideBadge: boolean = false;
16
+ private _small: boolean = false;
17
+ private _readOnly: boolean = false;
15
18
 
16
19
  @api
17
20
  get versions() {
@@ -51,6 +54,27 @@ export default class VersionPicker extends LightningElement {
51
54
  this._hideBadge = normalizeBoolean(value);
52
55
  }
53
56
 
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
+
54
78
  private get showVersionPicker() {
55
79
  return this._versions && this._versions.length !== 0;
56
80
  }
@@ -59,6 +83,62 @@ export default class VersionPicker extends LightningElement {
59
83
  return !this.hideBadge;
60
84
  }
61
85
 
86
+ private get readOnlyClass(): string {
87
+ return cx(
88
+ "version-picker-readonly",
89
+ this.small && "version-picker-readonly-small"
90
+ );
91
+ }
92
+
93
+ private get dotClass(): string {
94
+ return `version-picker-dot ${
95
+ this.latestVersion
96
+ ? "version-picker-dot-latest"
97
+ : "version-picker-dot-not-latest"
98
+ }`;
99
+ }
100
+
101
+ // Accessible name for the small trigger: includes the latest state so it
102
+ // isn't conveyed by the dot's color alone (WCAG 1.4.1). Omits the state
103
+ // when the indicator is hidden.
104
+ private get smallTriggerAriaLabel(): string {
105
+ const label = this.selectedVersion?.label ?? "";
106
+ if (!this.showLatestTag) {
107
+ return label;
108
+ }
109
+ return `${label}, ${this.latestVersion ? "Latest" : "Not Latest"}`;
110
+ }
111
+
112
+ // --- Per-variant values for the shared dropdown/button markup below. The
113
+ // small variant opts into extra classes and a compact chevron.
114
+
115
+ // Not cx(): the default must have NO class attribute (cx returns "", which
116
+ // would render class="" and break the frozen consumer snapshots).
117
+ private get dropdownClass(): string | undefined {
118
+ return this.small ? "version-picker-dropdown-small" : undefined;
119
+ }
120
+
121
+ private get triggerClass(): string {
122
+ return cx(
123
+ "version-picker-button",
124
+ this.small && "version-picker-button-small"
125
+ );
126
+ }
127
+
128
+ private get triggerIconSize(): string {
129
+ return this.small ? "xsmall" : "medium";
130
+ }
131
+
132
+ // Default returns "" (dx-button's own default) so the trigger's rendered
133
+ // aria-label is unchanged for the default variant.
134
+ private get triggerAriaLabel(): string {
135
+ return this.small ? this.smallTriggerAriaLabel : "";
136
+ }
137
+
138
+ private get selectedVersionClass(): string {
139
+ return cx("selected-version", this.small && "selected-version-small");
140
+ }
141
+
62
142
  private onVersionChange(e: CustomEvent) {
63
143
  this.dispatchEvent(new CustomEvent("change", { detail: e.detail }));
64
144
  }
package/LICENSE DELETED
@@ -1,12 +0,0 @@
1
- Copyright (c) 2020, Salesforce.com, Inc.
2
- All rights reserved.
3
-
4
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
-
6
- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
-
8
- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
-
10
- * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
-
12
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.