@salesforcedevs/dx-components 1.40.6 → 1.40.7-bug-fix-alpha1

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.
Files changed (29) hide show
  1. package/lwc.config.json +3 -2
  2. package/package.json +46 -47
  3. package/src/modules/dx/button/button.css +2 -2
  4. package/src/modules/dx/dropdown/dropdown.css +29 -1
  5. package/src/modules/dx/dropdown/dropdown.html +1 -1
  6. package/src/modules/dx/dropdown/dropdown.ts +16 -1
  7. package/src/modules/dx/dropdownOption/dropdownOption.css +29 -6
  8. package/src/modules/dx/popover/popover.ts +119 -7
  9. package/src/modules/dx/searchHybrid/searchHybrid.html +1 -1
  10. package/src/modules/dx/searchHybrid/searchHybrid.ts +4 -0
  11. package/src/modules/dx/sidebar/sidebar.html +1 -0
  12. package/src/modules/dx/sidebarOld/sidebarOld.html +1 -0
  13. package/src/modules/dx/tree/tree.html +1 -0
  14. package/src/modules/dx/tree/tree.ts +5 -1
  15. package/src/modules/dx/tree/treeHandler.ts +92 -13
  16. package/src/modules/dx/treeItem/treeItem.css +39 -0
  17. package/src/modules/dx/treeItem/treeItem.html +17 -2
  18. package/src/modules/dx/treeItem/treeItem.ts +109 -11
  19. package/src/modules/dx/treeTile/treeTile.css +57 -0
  20. package/src/modules/dx/treeTile/treeTile.ts +12 -3
  21. package/src/modules/dx/typeBadge/typeBadge.ts +23 -2
  22. package/src/modules/dx/versionPicker/versionPicker.css +248 -0
  23. package/src/modules/dx/versionPicker/versionPicker.html +73 -0
  24. package/src/modules/dx/versionPicker/versionPicker.ts +157 -0
  25. package/src/modules/dxHelpers/commonSidebar/commonSidebar.css +3 -3
  26. package/src/modules/dxHelpers/commonTreeItem/commonTreeItem.css +4 -0
  27. package/src/modules/dxHelpers/typeBadge/typeBadge.css +22 -0
  28. package/src/modules/dxUtils/topicVersion/topicVersion.ts +525 -0
  29. package/LICENSE +0 -12
package/lwc.config.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "modules": [
3
3
  { "dir": "src/modules" },
4
- { "npm": "@salesforcedevs/dw-components" },
5
- { "npm": "@salesforcedevs/docs-components" }
4
+ { "npm": "@salesforcedevs/dw-components" }
6
5
  ],
7
6
  "expose": [
8
7
  "dx/agenda",
@@ -111,6 +110,7 @@
111
110
  "dx/tooltip",
112
111
  "dx/trafficLabeler",
113
112
  "dx/tree",
113
+ "dx/versionPicker",
114
114
  "dx/typeBadge",
115
115
  "dx/vimeoPlayer",
116
116
  "dxBaseElements/headerBase",
@@ -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.6",
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
- "gitHead": "4fd9e541d6670ba80769d42f64e0bf70e342a743"
48
- }
2
+ "name": "@salesforcedevs/dx-components",
3
+ "version": "1.40.7-bug-fix-alpha1",
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,12 +14,40 @@
14
14
  display: none;
15
15
  }
16
16
 
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
+ );
27
+ }
28
+
17
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
+ }
42
+
43
+ /* Nested fit-content menus (content action toolbar). Flat fit-content menus,
44
+ including the version picker, keep the option rules above. */
45
+ .menu-fit-content.menu-nested {
18
46
  --dx-c-popover-width: max-content;
19
47
  --dx-c-popover-padding: var(--dx-g-spacing-sm) 0;
20
48
  }
21
49
 
22
- .menu-fit-content .menu_list {
50
+ .menu-fit-content.menu-nested .menu_list {
23
51
  width: 100%;
24
52
  align-items: flex-start;
25
53
  gap: var(--dx-g-spacing-sm);
@@ -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,11 @@
25
25
  display: flex;
26
26
  font-family: var(--dx-g-font-sans);
27
27
  width: 100%;
28
- padding: var(--dx-g-spacing-sm);
29
- font-size: var(--dx-g-text-sm);
28
+
29
+ /* Opt-in hooks; defaults preserve today's look. */
30
+ padding: var(--dx-c-dropdown-option-padding, var(--dx-g-spacing-sm));
31
+ font-size: var(--dx-c-dropdown-option-font-size, var(--dx-g-text-sm));
32
+ border-radius: var(--dx-c-dropdown-option-border-radius, 0);
30
33
  transition: var(--dx-g-transition-hue-1x);
31
34
  align-items: center;
32
35
  outline: none !important;
@@ -47,7 +50,10 @@
47
50
  }
48
51
 
49
52
  .option-active {
50
- --dx-c-dropdown-option-label-color: var(--dx-g-blue-vibrant-50);
53
+ --dx-c-dropdown-option-label-color: var(
54
+ --dx-c-dropdown-option-active-label-color,
55
+ var(--dx-g-blue-vibrant-50)
56
+ );
51
57
 
52
58
  background: var(--dx-g-cloud-blue-vibrant-95);
53
59
  }
@@ -57,8 +63,12 @@
57
63
  flex-direction: column;
58
64
  justify-content: center;
59
65
  font-family: var(--dx-g-font-sans);
60
- font-size: var(--dx-g-text-sm);
66
+ font-size: var(--dx-c-dropdown-option-font-size, var(--dx-g-text-sm));
67
+ color: var(--dx-c-dropdown-option-label-color, inherit);
61
68
  text-align: left;
69
+
70
+ /* Opt-in: set to 0 to let a long label truncate instead of overflow. */
71
+ min-width: var(--dx-c-dropdown-option-min-width, auto);
62
72
  }
63
73
 
64
74
  .option-indented .option_details {
@@ -68,10 +78,20 @@
68
78
 
69
79
  .option_label {
70
80
  color: var(--dx-c-dropdown-option-label-color, var(--dx-g-blue-vibrant-50));
71
- display: flex;
81
+
82
+ /* Opt-in: set to block so text-overflow:ellipsis applies (ignored on flex). */
83
+ display: var(--dx-c-dropdown-option-label-display, flex);
72
84
  align-items: center;
73
85
  font-weight: var(--dx-c-dropdown-option-font-weight, var(--dx-g-font-bold));
74
86
  font-size: var(--dx-c-dropdown-option-font-size, var(--dx-g-text-base));
87
+
88
+ /* Opt-in truncation: consumers set --dx-c-dropdown-option-white-space:
89
+ nowrap to truncate labels with an ellipsis instead of wrapping.
90
+ Defaults preserve today's wrapping behavior. */
91
+ white-space: var(--dx-c-dropdown-option-white-space, normal);
92
+ overflow: var(--dx-c-dropdown-option-overflow, visible);
93
+ overflow-wrap: var(--dx-c-dropdown-option-overflow-wrap, normal);
94
+ text-overflow: var(--dx-c-dropdown-option-text-overflow, clip);
75
95
  }
76
96
 
77
97
  .option-indented .option_label {
@@ -130,7 +150,10 @@
130
150
  }
131
151
 
132
152
  .option-fit-content.option-active .option_label {
133
- color: var(--dx-g-blue-vibrant-50);
153
+ color: var(
154
+ --dx-c-dropdown-option-active-label-color,
155
+ var(--dx-g-blue-vibrant-50)
156
+ );
134
157
  }
135
158
 
136
159
  .option-fit-content .option_icon {
@@ -89,7 +89,15 @@ export default class Popover extends LightningElement {
89
89
 
90
90
  this.dispatchEvent(new CustomEvent("open"));
91
91
 
92
- setTimeout(() => {
92
+ this.clearOpenListenersTimer();
93
+ // Defer so the opening click is not treated as an outside click.
94
+ // Drop the callback if the popover closes before it runs, or the
95
+ // document listeners attach with nothing left to remove them.
96
+ this.openListenersTimer = setTimeout(() => {
97
+ this.openListenersTimer = undefined;
98
+ if (!this._open) {
99
+ return;
100
+ }
93
101
  this.setPosition();
94
102
  this.attachListenersTopopover();
95
103
  });
@@ -98,7 +106,15 @@ export default class Popover extends LightningElement {
98
106
 
99
107
  @api
100
108
  closePopover(focusControl: boolean = false) {
109
+ this.clearOpenListenersTimer();
110
+ if (!this._open) {
111
+ return;
112
+ }
101
113
  this._open = false;
114
+ // Hide before aria-expanded changes. :has([aria-expanded]) / :focus-within
115
+ // stacking rules would otherwise drop position:relative while this menu
116
+ // is still visible, flashing it against a different offset parent.
117
+ this.popover?.classList.remove("popover-container_open");
102
118
  this.autoUpdateCleanup?.();
103
119
  if (focusControl && this.control && this.control.focus) {
104
120
  this.control.focus();
@@ -106,20 +122,28 @@ export default class Popover extends LightningElement {
106
122
  if (this.control) {
107
123
  this.control.setAttribute("aria-expanded", "false");
108
124
  }
109
- document.removeEventListener("click", this.interactOutsideListener);
125
+ document.removeEventListener(
126
+ "click",
127
+ this.interactOutsideListener,
128
+ true
129
+ );
110
130
  document.removeEventListener("focusout", this.interactOutsideListener);
131
+ document.removeEventListener("keydown", this.onDocumentKeydown, true);
111
132
  if (this.openOnHover) {
112
133
  document.removeEventListener(
113
134
  "mousemove",
114
135
  this.interactOutsideListener
115
136
  );
116
137
  }
138
+ this.pointerOpened = false;
117
139
  this.dispatchEvent(new CustomEvent("close"));
118
140
  }
119
141
 
120
142
  // PRIVATE VARS
121
143
 
122
144
  private controlled: boolean = false;
145
+ private pointerOpened: boolean = false;
146
+ private openListenersTimer?: ReturnType<typeof setTimeout>;
123
147
  private _open: boolean = false;
124
148
  private _rendered: boolean = isPrerender();
125
149
  private _role: string | null = null;
@@ -132,9 +156,15 @@ export default class Popover extends LightningElement {
132
156
  // LIFECYCLE METHODS
133
157
 
134
158
  disconnectedCallback() {
135
- document.removeEventListener("click", this.interactOutsideListener);
159
+ this.clearOpenListenersTimer();
160
+ document.removeEventListener(
161
+ "click",
162
+ this.interactOutsideListener,
163
+ true
164
+ );
136
165
  document.removeEventListener("mousemove", this.interactOutsideListener);
137
166
  document.removeEventListener("focusout", this.interactOutsideListener);
167
+ document.removeEventListener("keydown", this.onDocumentKeydown, true);
138
168
  this.removeControlListeners();
139
169
  }
140
170
 
@@ -226,17 +256,91 @@ export default class Popover extends LightningElement {
226
256
  switch (e.key) {
227
257
  case "ArrowDown": {
228
258
  e.preventDefault();
259
+ this.pointerOpened = false;
229
260
  if (!this.open) {
230
261
  this.openPopover();
231
262
  }
232
263
  this.dispatchEvent(new CustomEvent("controlarrowdown"));
233
264
  break;
234
265
  }
266
+ case "Esc":
267
+ case "Escape":
268
+ this.closeOnEscape(e);
269
+ break;
235
270
  default:
236
271
  }
237
272
  };
238
273
 
239
- private onControlClick = () => {
274
+ // Capture on document: click-open leaves focus on the trigger, and slotted
275
+ // menu options live outside this shadow tree, so the panel's onkeydown
276
+ // never sees Escape. Only the innermost popover in the event path handles
277
+ // it, so Escape in another control does not close this menu or cancel that
278
+ // control's own Escape behavior.
279
+ private onDocumentKeydown = (e: KeyboardEvent) => {
280
+ if ((e.key !== "Escape" && e.key !== "Esc") || !this.handlesEscape(e)) {
281
+ return;
282
+ }
283
+ this.closeOnEscape(e);
284
+ };
285
+
286
+ private handlesEscape(e: KeyboardEvent): boolean {
287
+ if (!this._open || e.defaultPrevented) {
288
+ return false;
289
+ }
290
+ const host = this.template.host;
291
+ const path = e.composedPath();
292
+ for (const node of path) {
293
+ if (node instanceof Element && node.localName === "dx-popover") {
294
+ return node === host;
295
+ }
296
+ }
297
+ // A non-composed event fired in this shadow or on the trigger does
298
+ // not list the host. The panel and control listeners still apply.
299
+ return (
300
+ (this.popover !== null && path.includes(this.popover)) ||
301
+ (this.control !== null && path.includes(this.control))
302
+ );
303
+ }
304
+
305
+ private clearOpenListenersTimer() {
306
+ if (this.openListenersTimer !== undefined) {
307
+ clearTimeout(this.openListenersTimer);
308
+ this.openListenersTimer = undefined;
309
+ }
310
+ }
311
+
312
+ private closeOnEscape(e: KeyboardEvent) {
313
+ if (!this.open) {
314
+ return;
315
+ }
316
+ e.preventDefault();
317
+ this.dispatchRequestCloseEvent("keypress-escape");
318
+ // Click-open + Escape upgrades :focus-visible on the trigger. Blur so
319
+ // the closed picker shows hover (pointer still over it) or default,
320
+ // not the keyboard halo. Keyboard-opened menus still restore focus.
321
+ const openedWithPointer = this.pointerOpened;
322
+ this.closePopover(!openedWithPointer);
323
+ if (openedWithPointer) {
324
+ this.blurControl();
325
+ }
326
+ }
327
+
328
+ private blurControl() {
329
+ const host = this.control;
330
+ if (!host) {
331
+ return;
332
+ }
333
+ const inner = host.shadowRoot?.activeElement as HTMLElement | null;
334
+ if (inner && typeof inner.blur === "function") {
335
+ inner.blur();
336
+ return;
337
+ }
338
+ if (typeof host.blur === "function") {
339
+ host.blur();
340
+ }
341
+ }
342
+
343
+ private onControlClick = (e: MouseEvent) => {
240
344
  if (this.controlled && this.open) {
241
345
  this.dispatchRequestCloseEvent("click-control");
242
346
  return;
@@ -257,6 +361,10 @@ export default class Popover extends LightningElement {
257
361
  return;
258
362
  }
259
363
 
364
+ // Enter/Space on a button synthesizes a click with detail 0. Only a
365
+ // pointer click should blur the trigger on Escape; keyboard activation
366
+ // must leave focus on the control.
367
+ this.pointerOpened = e.detail > 0;
260
368
  this.openPopover();
261
369
  };
262
370
 
@@ -287,16 +395,20 @@ export default class Popover extends LightningElement {
287
395
  switch (e.key) {
288
396
  case "Esc":
289
397
  case "Escape":
290
- this.dispatchRequestCloseEvent("keypress-escape");
291
- this.closePopover(true);
398
+ this.closeOnEscape(e);
292
399
  break;
293
400
  default:
294
401
  }
295
402
  }
296
403
 
297
404
  private attachListenersTopopover() {
298
- document.addEventListener("click", this.interactOutsideListener);
405
+ // Capture so a click still counts as outside when a descendant
406
+ // calls stopPropagation (topic version pickers do, so the tree row
407
+ // does not navigate). Without this, opening a second picker leaves
408
+ // the first menu open.
409
+ document.addEventListener("click", this.interactOutsideListener, true);
299
410
  document.addEventListener("focusout", this.interactOutsideListener);
411
+ document.addEventListener("keydown", this.onDocumentKeydown, true);
300
412
  if (this.openOnHover) {
301
413
  document.addEventListener(
302
414
  "mousemove",
@@ -106,7 +106,7 @@
106
106
  {result.contentTypeLabel}
107
107
  </span>
108
108
  </div>
109
- <a href={result.url} class="dx-result-title">
109
+ <a href={result.href} class="dx-result-title">
110
110
  {result.title}
111
111
  </a>
112
112
  <p if:true={result.hasDate} class="dx-result-date">
@@ -8,6 +8,7 @@ import {
8
8
  decodeHtmlEntities,
9
9
  highlightedSnippetHtml,
10
10
  isSortOption,
11
+ resultHref,
11
12
  SORT_OPTIONS,
12
13
  type SearchResult,
13
14
  type SortOption
@@ -30,6 +31,8 @@ const SORT_LABELS: Record<SortOption, string> = {
30
31
  interface DisplayResult {
31
32
  key: string;
32
33
  url: string;
34
+ // Matched-section link; falls back to the page url.
35
+ href: string;
33
36
  title: string;
34
37
  description: string | null;
35
38
  hasDescription: boolean;
@@ -167,6 +170,7 @@ export default class SearchHybrid extends LightningElement {
167
170
  return {
168
171
  key: `${r.url}-${i}`,
169
172
  url: r.url,
173
+ href: resultHref(r),
170
174
  title: decodeHtmlEntities(r.title),
171
175
  description,
172
176
  hasDescription: description !== null,
@@ -102,6 +102,7 @@
102
102
  for:each={trees}
103
103
  for:item="tree"
104
104
  key={tree.key}
105
+ language={language}
105
106
  tree-root={tree.tree}
106
107
  onselect={onSelect}
107
108
  value={value}
@@ -71,6 +71,7 @@
71
71
  for:each={filteredTrees}
72
72
  for:item="tree"
73
73
  key={tree.key}
74
+ language={language}
74
75
  tree-root={tree.tree}
75
76
  onselect={onSelect}
76
77
  value={value}
@@ -4,6 +4,7 @@
4
4
  is-root
5
5
  tree-node={treeRoot}
6
6
  selected-key={selectedKey}
7
+ language={language}
7
8
  ontileselected={onTileSelected}
8
9
  onexpandcollapse={onExpandCollapse}
9
10
  onregister={onRegister}
@@ -4,6 +4,10 @@ import { toJson } from "dxUtils/normalizers";
4
4
  import { TreeHandler } from "./treeHandler";
5
5
 
6
6
  export default class Tree extends LightningElement {
7
+ // Must be set before `treeRoot`. Both sidebar templates pass `language`
8
+ // first; the tree resolves stored topic versions with it while being built.
9
+ @api language?: string;
10
+
7
11
  @api
8
12
  get treeRoot() {
9
13
  return this._treeRoot!;
@@ -11,7 +15,7 @@ export default class Tree extends LightningElement {
11
15
 
12
16
  set treeRoot(value: TreeNode | string) {
13
17
  value = toJson(value);
14
- this._treeHandler = new TreeHandler();
18
+ this._treeHandler = new TreeHandler(this.language);
15
19
  this._treeRoot = this._treeHandler.initialize(value as TreeNode);
16
20
  if (this._value) {
17
21
  this.selectedKey = this._treeHandler.getKeyByName(this._value);