@salesforcedevs/dx-components 1.40.4-new-brand → 1.40.5-topic-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/lwc.config.json CHANGED
@@ -140,6 +140,7 @@
140
140
  "dxUtils/lwc",
141
141
  "dxUtils/urlWatcher",
142
142
  "dxUtils/normalizers",
143
+ "dxUtils/topicVersion",
143
144
  "dxUtils/queryCoordinator",
144
145
  "dxUtils/recentSearches",
145
146
  "dxUtils/regexps",
package/package.json CHANGED
@@ -1,48 +1,47 @@
1
1
  {
2
- "name": "@salesforcedevs/dx-components",
3
- "version": "1.40.4-new-brand",
4
- "description": "DX Lightning web components",
5
- "license": "MIT",
6
- "engines": {
7
- "node": "22.x"
8
- },
9
- "publishConfig": {
10
- "access": "public"
11
- },
12
- "dependencies": {
13
- "@coveo/headless": "3.22.2",
14
- "@floating-ui/dom": "1.5.1",
15
- "@optimizely/optimizely-sdk": "5.3.0",
16
- "@shikijs/colorized-brackets": "^3.12.2",
17
- "@types/throttle-debounce": "5.0.2",
18
- "@vimeo/player": "2.22.0",
19
- "classnames": "2.5.1",
20
- "composed-offset-position": "0.0.4",
21
- "coveo-search-ui": "2.10113.0",
22
- "debounce": "1.2.1",
23
- "js-cookie": "3.0.5",
24
- "lodash.defaults": "4.2.0",
25
- "lodash.get": "4.4.2",
26
- "lodash.kebabcase": "4.1.1",
27
- "memoize-one": "6.0.0",
28
- "microtip": "0.2.2",
29
- "shiki": "^3.12.2",
30
- "throttle-debounce": "5.0.0",
31
- "uuid": "9.0.1"
32
- },
33
- "devDependencies": {
34
- "@types/classnames": "2.3.1",
35
- "@types/debounce": "1.2.4",
36
- "@types/js-cookie": "3.0.6",
37
- "@types/lodash.defaults": "4.2.9",
38
- "@types/lodash.get": "4.4.9",
39
- "@types/lodash.kebabcase": "4.1.9",
40
- "@types/luxon": "3.4.2",
41
- "@types/uuid": "8.3.4",
42
- "@types/vimeo__player": "2.18.3",
43
- "eventsourcemock": "2.0.0",
44
- "luxon": "3.4.4",
45
- "msw": "^2.12.4"
46
- },
47
- "stableVersion": "1.40.4"
48
- }
2
+ "name": "@salesforcedevs/dx-components",
3
+ "version": "1.40.5-topic-alpha.1",
4
+ "description": "DX Lightning web components",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": "22.x"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "dependencies": {
13
+ "@coveo/headless": "3.22.2",
14
+ "@floating-ui/dom": "1.5.1",
15
+ "@optimizely/optimizely-sdk": "5.3.0",
16
+ "@shikijs/colorized-brackets": "^3.12.2",
17
+ "@types/throttle-debounce": "5.0.2",
18
+ "@vimeo/player": "2.22.0",
19
+ "classnames": "2.5.1",
20
+ "composed-offset-position": "0.0.4",
21
+ "coveo-search-ui": "2.10113.0",
22
+ "debounce": "1.2.1",
23
+ "js-cookie": "3.0.5",
24
+ "lodash.defaults": "4.2.0",
25
+ "lodash.get": "4.4.2",
26
+ "lodash.kebabcase": "4.1.1",
27
+ "memoize-one": "6.0.0",
28
+ "microtip": "0.2.2",
29
+ "shiki": "^3.12.2",
30
+ "throttle-debounce": "5.0.0",
31
+ "uuid": "9.0.1"
32
+ },
33
+ "devDependencies": {
34
+ "@types/classnames": "2.3.1",
35
+ "@types/debounce": "1.2.4",
36
+ "@types/js-cookie": "3.0.6",
37
+ "@types/lodash.defaults": "4.2.9",
38
+ "@types/lodash.get": "4.4.9",
39
+ "@types/lodash.kebabcase": "4.1.9",
40
+ "@types/luxon": "3.4.2",
41
+ "@types/uuid": "8.3.4",
42
+ "@types/vimeo__player": "2.18.3",
43
+ "eventsourcemock": "2.0.0",
44
+ "luxon": "3.4.4",
45
+ "msw": "^2.12.4"
46
+ }
47
+ }
@@ -86,11 +86,11 @@
86
86
  }
87
87
 
88
88
  .button.icon-right:not(.slot-empty) > *:nth-child(2) {
89
- margin-left: var(--dx-g-spacing-sm);
89
+ margin-left: var(--dx-c-button-icon-gap, var(--dx-g-spacing-sm));
90
90
  }
91
91
 
92
92
  .button.icon-left:not(.slot-empty) > *:nth-child(2) {
93
- margin-right: var(--dx-g-spacing-sm);
93
+ margin-right: var(--dx-c-button-icon-gap, var(--dx-g-spacing-sm));
94
94
  }
95
95
 
96
96
  .button.style-icon.slot-empty {
@@ -14,30 +14,30 @@
14
14
  display: none;
15
15
  }
16
16
 
17
- .menu-fit-content {
18
- --dx-c-popover-width: max-content;
19
- --dx-c-popover-padding: var(--dx-g-spacing-sm) 0;
17
+ /* Opt-in fit-content menu: hugs the labels (so it can be as narrow as the
18
+ shortest one) and stops at --dx-c-dropdown-menu-max-width. Defaults still
19
+ truncate; set --dx-c-dropdown-fit-white-space: normal to wrap instead. */
20
+ .menu-fit-content::part(content) {
21
+ box-sizing: border-box;
22
+ width: max-content;
23
+ max-width: var(
24
+ --dx-c-dropdown-menu-max-width,
25
+ calc(100vw - var(--dx-g-spacing-lg))
26
+ );
20
27
  }
21
28
 
22
- .menu-fit-content .menu_list {
23
- width: 100%;
24
- align-items: flex-start;
25
- gap: var(--dx-g-spacing-sm);
26
- }
27
-
28
- .menu-fit-content.menu-nested .menu_option:not(:first-child) {
29
- border-top: var(--border);
30
- margin-top: 0;
31
- padding-top: var(--dx-g-spacing-sm);
32
- }
33
-
34
- .menu-fit-content .menu_heading {
35
- margin: 0;
36
- padding: var(--dx-g-spacing-smd) var(--dx-g-spacing-lg);
37
- font-family: var(--dx-g-font-sans);
38
- line-height: var(--dx-g-spacing-mlg);
39
- text-transform: none;
40
- color: var(--dx-g-gray-10);
29
+ .menu-fit-content {
30
+ --dx-c-dropdown-option-min-width: 0;
31
+ --dx-c-dropdown-option-label-display: block;
32
+ --dx-c-dropdown-option-white-space: var(
33
+ --dx-c-dropdown-fit-white-space,
34
+ nowrap
35
+ );
36
+ --dx-c-dropdown-option-overflow: var(--dx-c-dropdown-fit-overflow, hidden);
37
+ --dx-c-dropdown-option-text-overflow: var(
38
+ --dx-c-dropdown-fit-text-overflow,
39
+ ellipsis
40
+ );
41
41
  }
42
42
 
43
43
  @media screen and (max-width: 1000px) {
@@ -9,7 +9,7 @@
9
9
  open-on-hover={openOnHover}
10
10
  open={open}
11
11
  page-padding={pagePadding}
12
- placement={placement}
12
+ placement={effectivePlacement}
13
13
  small={small}
14
14
  width={effectiveWidth}
15
15
  >
@@ -82,20 +82,35 @@ export default class Dropdown extends LightningElement {
82
82
  return this.areOptionsEmpty ? null : "menu";
83
83
  }
84
84
 
85
+ // "fit-end" is fit-content, aligned to the trigger's end edge.
86
+ // "full" matches the trigger width. "fixed" and "fit-content" are unchanged.
85
87
  private get isFitContent() {
86
- return this.widthMode === "fit-content";
88
+ return this.widthMode === "fit-content" || this.widthMode === "fit-end";
87
89
  }
88
90
 
91
+ private get effectivePlacement(): PopperPlacement | undefined {
92
+ return this.widthMode === "fit-end" ? "bottom-end" : this.placement;
93
+ }
94
+
95
+ // fit-content takes precedence; the misuse is surfaced in className() below.
89
96
  private get effectiveWidth() {
90
97
  return this.isFitContent ? null : this.width;
91
98
  }
92
99
 
93
100
  private get effectiveFullWidth() {
101
+ if (this.widthMode === "full") {
102
+ return true;
103
+ }
94
104
  return this.isFitContent ? false : this.fullWidth;
95
105
  }
96
106
 
97
107
  private get className() {
98
108
  const fitContent = this.isFitContent;
109
+ if (fitContent && (this.width || this.fullWidth)) {
110
+ console.error(
111
+ 'The "width-mode=fit-content" and "width"/"full-width" properties cannot be provided simultaneously. "width-mode=fit-content" is prioritized; "width"/"full-width" are ignored.'
112
+ );
113
+ }
99
114
  return cx(
100
115
  this.isNested && "menu-nested",
101
116
  this.isNested &&
@@ -25,8 +25,14 @@
25
25
  display: flex;
26
26
  font-family: var(--dx-g-font-sans);
27
27
  width: 100%;
28
- padding: var(--dx-g-spacing-sm);
28
+
29
+ /* Opt-in hooks; defaults preserve today's look. */
30
+ padding: var(--dx-c-dropdown-option-padding, var(--dx-g-spacing-sm));
29
31
  font-size: var(--dx-g-text-sm);
32
+ border-radius: var(
33
+ --dx-c-dropdown-option-border-radius,
34
+ var(--dx-g-spacing-xs)
35
+ );
30
36
  transition: var(--dx-g-transition-hue-1x);
31
37
  align-items: center;
32
38
  outline: none !important;
@@ -59,6 +65,9 @@
59
65
  font-family: var(--dx-g-font-sans);
60
66
  font-size: var(--dx-g-text-sm);
61
67
  text-align: left;
68
+
69
+ /* Opt-in: set to 0 to let a long label truncate instead of overflow. */
70
+ min-width: var(--dx-c-dropdown-option-min-width, auto);
62
71
  }
63
72
 
64
73
  .option-indented .option_details {
@@ -68,10 +77,20 @@
68
77
 
69
78
  .option_label {
70
79
  color: var(--dx-c-dropdown-option-label-color, var(--dx-g-blue-vibrant-50));
71
- display: flex;
80
+
81
+ /* Opt-in: set to block so text-overflow:ellipsis applies (ignored on flex). */
82
+ display: var(--dx-c-dropdown-option-label-display, flex);
72
83
  align-items: center;
73
84
  font-weight: var(--dx-c-dropdown-option-font-weight, var(--dx-g-font-bold));
74
85
  font-size: var(--dx-c-dropdown-option-font-size, var(--dx-g-text-base));
86
+
87
+ /* Opt-in truncation: consumers set --dx-c-dropdown-option-white-space:
88
+ nowrap to truncate labels with an ellipsis instead of wrapping.
89
+ Defaults preserve today's wrapping behavior. */
90
+ white-space: var(--dx-c-dropdown-option-white-space, normal);
91
+ overflow: var(--dx-c-dropdown-option-overflow, visible);
92
+ overflow-wrap: var(--dx-c-dropdown-option-overflow-wrap, normal);
93
+ text-overflow: var(--dx-c-dropdown-option-text-overflow, clip);
75
94
  }
76
95
 
77
96
  .option-indented .option_label {
@@ -106,7 +106,11 @@ export default class Popover extends LightningElement {
106
106
  if (this.control) {
107
107
  this.control.setAttribute("aria-expanded", "false");
108
108
  }
109
- document.removeEventListener("click", this.interactOutsideListener);
109
+ document.removeEventListener(
110
+ "click",
111
+ this.interactOutsideListener,
112
+ true
113
+ );
110
114
  document.removeEventListener("focusout", this.interactOutsideListener);
111
115
  if (this.openOnHover) {
112
116
  document.removeEventListener(
@@ -132,7 +136,11 @@ export default class Popover extends LightningElement {
132
136
  // LIFECYCLE METHODS
133
137
 
134
138
  disconnectedCallback() {
135
- document.removeEventListener("click", this.interactOutsideListener);
139
+ document.removeEventListener(
140
+ "click",
141
+ this.interactOutsideListener,
142
+ true
143
+ );
136
144
  document.removeEventListener("mousemove", this.interactOutsideListener);
137
145
  document.removeEventListener("focusout", this.interactOutsideListener);
138
146
  this.removeControlListeners();
@@ -295,7 +303,11 @@ export default class Popover extends LightningElement {
295
303
  }
296
304
 
297
305
  private attachListenersTopopover() {
298
- document.addEventListener("click", this.interactOutsideListener);
306
+ // Capture so a click still counts as outside when a descendant
307
+ // calls stopPropagation (topic version pickers do, so the tree row
308
+ // does not navigate). Without this, opening a second picker leaves
309
+ // the first menu open.
310
+ document.addEventListener("click", this.interactOutsideListener, true);
299
311
  document.addEventListener("focusout", this.interactOutsideListener);
300
312
  if (this.openOnHover) {
301
313
  document.addEventListener(
@@ -1,6 +1,18 @@
1
1
  import type { InternalTreeNode, TreeNode } from "typings/custom";
2
+ import type { TopicVersionOption } from "dxUtils/topicVersion";
3
+ import {
4
+ applyVersionToHref,
5
+ normalizePath,
6
+ resolveSelectedVersion
7
+ } from "dxUtils/topicVersion";
8
+
9
+ type VersionedTreeNode = TreeNode & { versions?: TopicVersionOption[] };
10
+ type VersionedInternalTreeNode = InternalTreeNode & {
11
+ versions?: TopicVersionOption[];
12
+ };
13
+
2
14
  type MapItem = {
3
- node: InternalTreeNode;
15
+ node: VersionedInternalTreeNode;
4
16
  parent?: MapItem;
5
17
  setExpanded?: Function;
6
18
  };
@@ -21,7 +33,10 @@ export class TreeHandler {
21
33
  }
22
34
 
23
35
  getKeyByName(name: string): string | undefined {
24
- return name && this._nameKeyMap[name];
36
+ if (!name) {
37
+ return undefined;
38
+ }
39
+ return this._nameKeyMap[name] || this._nameKeyMap[normalizePath(name)];
25
40
  }
26
41
 
27
42
  initialize(treeRoot: TreeNode): InternalTreeNode {
@@ -48,12 +63,41 @@ export class TreeHandler {
48
63
  }
49
64
 
50
65
  private convert(
51
- node: TreeNode,
66
+ node: VersionedTreeNode,
52
67
  level: number = 0,
53
68
  parent?: MapItem,
54
- position?: number
55
- ): InternalTreeNode {
56
- const convertedNode: InternalTreeNode = {
69
+ position?: number,
70
+ ancestorVersion?: {
71
+ selected: TopicVersionOption;
72
+ versions: TopicVersionOption[];
73
+ }
74
+ ): VersionedInternalTreeNode {
75
+ const ownSelected = node.versions?.length
76
+ ? resolveSelectedVersion(node)
77
+ : undefined;
78
+ const versionContext =
79
+ ownSelected && node.versions
80
+ ? { selected: ownSelected, versions: node.versions }
81
+ : ancestorVersion;
82
+
83
+ let link = node.link;
84
+ if (
85
+ versionContext &&
86
+ !node.versions?.length &&
87
+ link?.href &&
88
+ link.target !== "_blank"
89
+ ) {
90
+ const rewritten = applyVersionToHref(
91
+ link.href,
92
+ versionContext.selected,
93
+ versionContext.versions
94
+ );
95
+ if (rewritten && rewritten !== link.href) {
96
+ link = { ...link, href: rewritten };
97
+ }
98
+ }
99
+
100
+ const convertedNode: VersionedInternalTreeNode = {
57
101
  label: node.label,
58
102
  name: node.name,
59
103
  key: this.calculateKey(
@@ -63,24 +107,47 @@ export class TreeHandler {
63
107
  level: level,
64
108
  isExpanded: node.isExpanded || false,
65
109
  isChildrenLoading: node.isChildrenLoading || false,
66
- link: node.link,
110
+ link,
67
111
  method: node.method,
68
112
  showForwardArrow: node.showForwardArrow,
69
- redirectToChild: node.redirectToChild
113
+ versions: node.versions
70
114
  };
71
115
 
72
116
  const mapItem = { node: convertedNode, parent };
73
117
  if (node.children && node.children.length > 0) {
74
118
  convertedNode.children = node.children.map((child, index) =>
75
- this.convert(child, level + 1, mapItem, index)
119
+ this.convert(child, level + 1, mapItem, index, versionContext)
76
120
  );
77
121
  }
78
122
 
79
123
  this._nodeMap[convertedNode.key] = mapItem;
80
- this._nameKeyMap[convertedNode.name] = convertedNode.key;
124
+ this.indexName(convertedNode.name, convertedNode.key);
125
+ this.indexName(convertedNode.link?.href, convertedNode.key);
126
+ // Index this topic's own versioned URLs only. Parent-only switching
127
+ // URLs point at a child with content and must not steal that child's
128
+ // selection.
129
+ if (node.name && node.versions) {
130
+ for (const version of node.versions) {
131
+ this.indexName(
132
+ applyVersionToHref(node.name, version, node.versions),
133
+ convertedNode.key
134
+ );
135
+ }
136
+ }
81
137
  return convertedNode;
82
138
  }
83
139
 
140
+ private indexName(name: string | undefined, key: string): void {
141
+ if (!name) {
142
+ return;
143
+ }
144
+ this._nameKeyMap[name] = key;
145
+ const normalized = normalizePath(name);
146
+ if (normalized && normalized !== name) {
147
+ this._nameKeyMap[normalized] = key;
148
+ }
149
+ }
150
+
84
151
  private calculateKey(parentKey: string, position?: number): string {
85
152
  if (position === undefined) {
86
153
  return parentKey;
@@ -4,6 +4,33 @@
4
4
  display: block;
5
5
  }
6
6
 
7
+ /* Lift this row above later sibling pickers while its menu is open. */
8
+ :host(:focus-within) {
9
+ position: relative;
10
+ z-index: 2;
11
+ }
12
+
13
+ .tree-item-row {
14
+ display: flex;
15
+ align-items: center;
16
+ min-width: 0;
17
+ }
18
+
19
+ .tree-item-row dx-tree-tile {
20
+ flex: 1;
21
+ min-width: 0;
22
+ }
23
+
24
+ .tree-item-version-picker {
25
+ flex: 0 0 auto;
26
+ margin-right: var(--dx-g-spacing-lg);
27
+ }
28
+
29
+ .tree-item-row-selected {
30
+ background: var(--dx-g-cloud-blue-vibrant-95);
31
+ box-shadow: inset var(--dx-g-spacing-xs) 0 0 0 var(--dx-g-blue-vibrant-40);
32
+ }
33
+
7
34
  button {
8
35
  width: 100%;
9
36
  text-align: left;
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <a href={href} target={target} onclick={onLinkClick} tabindex="-1">
2
+ <div class={rowClass}>
3
3
  <dx-tree-tile
4
4
  href={href}
5
5
  is-root={isRoot}
@@ -7,12 +7,26 @@
7
7
  is-expanded={isExpanded}
8
8
  has-children={hasChildren}
9
9
  is-selected={isSelected}
10
+ is-contained-selection={isContainedSelection}
10
11
  oniconclick={onIconClick}
11
12
  show-arrow={isParent}
12
13
  target={target}
13
14
  is-external-link={isExternalLink}
15
+ has-version-picker={showVersionPicker}
16
+ onclick={onLinkClick}
14
17
  ></dx-tree-tile>
15
- </a>
18
+ <doc-version-picker
19
+ if:true={showVersionPicker}
20
+ class="tree-item-version-picker"
21
+ small="true"
22
+ versions={versionPickerOptions}
23
+ selected-version={selectedVersion}
24
+ latest-version={isLatestVersion}
25
+ onchange={onVersionChange}
26
+ onclick={onVersionPickerClick}
27
+ onmousedown={onVersionPickerClick}
28
+ ></doc-version-picker>
29
+ </div>
16
30
  <img
17
31
  if:true={showChildrenLoading}
18
32
  class="tree-children-loading"
@@ -2,9 +2,24 @@ import { LightningElement, api } from "lwc";
2
2
  import { InternalTreeNode } from "typings/custom";
3
3
  import { track } from "dxUtils/analytics";
4
4
  import { isInViewport } from "dxUtils/browser";
5
+ import type { TopicVersionOption } from "dxUtils/topicVersion";
6
+ import {
7
+ firstContentNode,
8
+ getTopicVersionKey,
9
+ getVersionPickerOptions,
10
+ isParentOnlyTopic,
11
+ normalizePath,
12
+ persistTopicVersion,
13
+ resolveSelectedVersion,
14
+ shouldShowTopicVersionPicker
15
+ } from "dxUtils/topicVersion";
5
16
 
6
17
  const DEFAULT_TARGET = "_self";
7
18
 
19
+ type VersionedInternalTreeNode = InternalTreeNode & {
20
+ versions?: TopicVersionOption[];
21
+ };
22
+
8
23
  export default class TreeItem extends LightningElement {
9
24
  @api selectedKey?: string;
10
25
  @api isRoot: boolean = false;
@@ -20,7 +35,7 @@ export default class TreeItem extends LightningElement {
20
35
  this.isChildrenLoading = (node && node.isChildrenLoading) || false;
21
36
  }
22
37
 
23
- private _treeNode!: InternalTreeNode;
38
+ private _treeNode!: VersionedInternalTreeNode;
24
39
  private isExpanded: boolean = false;
25
40
  private isChildrenLoading: boolean = false;
26
41
  private rootParentLabel: string = "";
@@ -30,9 +45,22 @@ export default class TreeItem extends LightningElement {
30
45
  }
31
46
 
32
47
  private get isSelected(): boolean {
48
+ if (isParentOnlyTopic(this._treeNode)) {
49
+ return false;
50
+ }
33
51
  return this._treeNode.key === this.selectedKey && !this.isExternalLink;
34
52
  }
35
53
 
54
+ private get rowClass(): string {
55
+ return this.isSelected && this.showVersionPicker
56
+ ? "tree-item-row tree-item-row-selected"
57
+ : "tree-item-row";
58
+ }
59
+
60
+ private get isContainedSelection(): boolean {
61
+ return this.isSelected && this.showVersionPicker;
62
+ }
63
+
36
64
  private get hasChildren(): boolean {
37
65
  return !!(
38
66
  this.treeNode &&
@@ -83,8 +111,44 @@ export default class TreeItem extends LightningElement {
83
111
  return `${parentLabels.reverse().join(":")}:${this.treeNode.label}`;
84
112
  }
85
113
 
114
+ private get versions(): TopicVersionOption[] | undefined {
115
+ return this._treeNode?.versions;
116
+ }
117
+
118
+ private get versionPickerOptions(): TopicVersionOption[] | undefined {
119
+ return getVersionPickerOptions(this._treeNode);
120
+ }
121
+
122
+ private get showVersionPicker(): boolean {
123
+ return shouldShowTopicVersionPicker(
124
+ this.versions,
125
+ this._treeNode?.showForwardArrow
126
+ );
127
+ }
128
+
129
+ private get selectedVersion(): TopicVersionOption | undefined {
130
+ return resolveSelectedVersion(this._treeNode);
131
+ }
132
+
133
+ private get isLatestVersion(): boolean {
134
+ return (
135
+ !!this.selectedVersion &&
136
+ this.selectedVersion.id === this.versions?.[0]?.id
137
+ );
138
+ }
139
+
86
140
  private get href(): string | undefined {
141
+ if (isParentOnlyTopic(this._treeNode)) {
142
+ const contentNode = firstContentNode(this._treeNode);
143
+ return (
144
+ contentNode?.link?.href ||
145
+ contentNode?.name ||
146
+ this.selectedVersion?.link?.href ||
147
+ ""
148
+ );
149
+ }
87
150
  return (
151
+ this.selectedVersion?.link?.href ||
88
152
  (this._treeNode.link && this._treeNode.link.href) ||
89
153
  this._treeNode.name ||
90
154
  ""
@@ -104,6 +168,23 @@ export default class TreeItem extends LightningElement {
104
168
  setExpanded: this.setExpanded.bind(this)
105
169
  });
106
170
  }
171
+
172
+ // Remember this topic's resolved version (own URL, descendant URL, or
173
+ // session) so sibling pickers stay independent for the tab.
174
+ const selected = resolveSelectedVersion(this._treeNode);
175
+ const topicKey = getTopicVersionKey(this._treeNode);
176
+ if (this.versions?.length && selected && topicKey) {
177
+ persistTopicVersion(topicKey, selected.id);
178
+ }
179
+ }
180
+
181
+ private onVersionChange(event: CustomEvent): void {
182
+ event.stopPropagation();
183
+ persistTopicVersion(getTopicVersionKey(this._treeNode), event.detail);
184
+ }
185
+
186
+ private onVersionPickerClick(event: Event): void {
187
+ event.stopPropagation();
107
188
  }
108
189
 
109
190
  renderedCallback(): void {
@@ -134,8 +215,8 @@ export default class TreeItem extends LightningElement {
134
215
  private preventDefaultLinkBehavior(event: Event) {
135
216
  // prevent page refresh if href isn't present or link is already active
136
217
  if (
137
- !this._treeNode.link?.href ||
138
- this._treeNode.link?.href === window.location.pathname
218
+ !this.href ||
219
+ normalizePath(this.href) === normalizePath(window.location.pathname)
139
220
  ) {
140
221
  event.preventDefault();
141
222
  }
@@ -167,8 +248,12 @@ export default class TreeItem extends LightningElement {
167
248
  }
168
249
 
169
250
  this.sendGtm(event);
251
+ const contentNode = isParentOnlyTopic(this._treeNode)
252
+ ? firstContentNode(this._treeNode)
253
+ : undefined;
170
254
  this.fireEvent("tileselected", {
171
- name: this._treeNode.name,
255
+ name: contentNode?.name || this._treeNode.name,
256
+ ...(contentNode?.key ? { key: contentNode.key } : {}),
172
257
  isExternalLink: this.isExternalLink
173
258
  });
174
259
  }
@@ -186,8 +271,8 @@ export default class TreeItem extends LightningElement {
186
271
  this.dispatchEvent(
187
272
  new CustomEvent(type, {
188
273
  detail: {
189
- ...detail,
190
- key: this._treeNode.key
274
+ key: this._treeNode.key,
275
+ ...detail
191
276
  },
192
277
  bubbles: true,
193
278
  composed: true
@@ -66,6 +66,10 @@
66
66
  white-space: nowrap;
67
67
  }
68
68
 
69
+ .tile-with-version-picker {
70
+ padding-right: var(--dx-g-spacing-xs);
71
+ }
72
+
69
73
  .tile-with-children .tile-label {
70
74
  order: 2;
71
75
  }