@salesforcedevs/dx-components 1.40.4 → 1.40.5-topic-alpha.2

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/dx-components",
3
- "version": "1.40.4",
3
+ "version": "1.40.5-topic-alpha.2",
4
4
  "description": "DX Lightning web components",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -43,6 +43,5 @@
43
43
  "eventsourcemock": "2.0.0",
44
44
  "luxon": "3.4.4",
45
45
  "msw": "^2.12.4"
46
- },
47
- "gitHead": "37647f2998066bc5a3e345e3bfdbd70dfefa5adc"
46
+ }
48
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 {
@@ -1,5 +1,11 @@
1
1
  @import "dxHelpers/reset";
2
2
 
3
+ /* Options run edge to edge; the menu adds space above the first and below
4
+ the last. */
5
+ dx-popover {
6
+ --dx-c-popover-padding: var(--dx-g-spacing-sm) 0;
7
+ }
8
+
3
9
  .menu-nested:not(.menu-fit-content)::part(content) {
4
10
  padding-top: var(--dx-g-spacing-sm);
5
11
  width: 354px;
@@ -14,12 +20,22 @@
14
20
  display: none;
15
21
  }
16
22
 
23
+ /* fit-content and fit-end: the menu is as wide as its longest label. */
17
24
  .menu-fit-content {
18
25
  --dx-c-popover-width: max-content;
19
- --dx-c-popover-padding: var(--dx-g-spacing-sm) 0;
20
26
  }
21
27
 
22
- .menu-fit-content .menu_list {
28
+ /* fit-end also stops at --dx-c-dropdown-menu-max-width. Labels wrap only once
29
+ the menu reaches that width. */
30
+ .menu-fit-end::part(content) {
31
+ box-sizing: border-box;
32
+ max-width: var(
33
+ --dx-c-dropdown-menu-max-width,
34
+ calc(100vw - var(--dx-g-spacing-lg))
35
+ );
36
+ }
37
+
38
+ .menu-fit-content.menu-nested .menu_list {
23
39
  width: 100%;
24
40
  align-items: flex-start;
25
41
  gap: var(--dx-g-spacing-sm);
@@ -31,15 +47,6 @@
31
47
  padding-top: var(--dx-g-spacing-sm);
32
48
  }
33
49
 
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);
41
- }
42
-
43
50
  @media screen and (max-width: 1000px) {
44
51
  .menu-nested .menu_option:not(:first-child) {
45
52
  border-top: var(--border);
@@ -91,12 +98,13 @@
91
98
 
92
99
  .menu_heading {
93
100
  display: block;
94
- color: var(--dx-g-blue-vibrant-20);
95
- font-family: var(--dx-g-font-display);
101
+ margin: 0;
102
+ padding: var(--dx-g-spacing-smd) var(--dx-g-spacing-lg);
103
+ color: var(--dx-g-gray-10);
104
+ font-family: var(--dx-g-font-sans);
96
105
  font-size: var(--dx-g-text-sm);
97
106
  font-weight: bold;
98
- text-transform: uppercase;
99
- margin: var(--dx-g-spacing-sm);
107
+ line-height: var(--dx-g-spacing-mlg);
100
108
  }
101
109
 
102
110
  .callout_link {
@@ -105,7 +113,7 @@
105
113
  display: block;
106
114
  padding-top: var(--dx-g-spacing-xl);
107
115
  margin-top: auto;
108
- margin-left: var(--dx-g-spacing-sm);
116
+ margin-left: var(--dx-g-spacing-lg);
109
117
  }
110
118
 
111
119
  .empty {
@@ -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
  >
@@ -35,6 +35,7 @@
35
35
  nav-item-label={navItemLabel}
36
36
  variant={variant}
37
37
  width-mode={widthMode}
38
+ show-icon={showOptionIcons}
38
39
  ></dx-dropdown-option>
39
40
  <div
40
41
  key={option.id}
@@ -55,6 +56,7 @@
55
56
  option={suboption}
56
57
  nav-item-label={navItemLabel}
57
58
  width-mode={widthMode}
59
+ show-icon={showOptionIcons}
58
60
  ></dx-dropdown-option>
59
61
  </template>
60
62
  <dx-button
@@ -39,6 +39,7 @@ export default class Dropdown extends LightningElement {
39
39
  @api navItemLabel: string | null = null;
40
40
  @api variant: DropdownVariant = "base";
41
41
  @api widthMode: DropdownWidthMode = "fixed";
42
+ @api showOptionIcons?: boolean = false; // right-aligned option icons (opt-in)
42
43
 
43
44
  // props forwarded to dropdown option
44
45
  @api analyticsEvent?: string;
@@ -82,20 +83,35 @@ export default class Dropdown extends LightningElement {
82
83
  return this.areOptionsEmpty ? null : "menu";
83
84
  }
84
85
 
86
+ // "fit-end" is fit-content, aligned to the trigger's end edge.
87
+ // "full" matches the trigger width. "fixed" and "fit-content" are unchanged.
85
88
  private get isFitContent() {
86
- return this.widthMode === "fit-content";
89
+ return this.widthMode === "fit-content" || this.widthMode === "fit-end";
87
90
  }
88
91
 
92
+ private get effectivePlacement(): PopperPlacement | undefined {
93
+ return this.widthMode === "fit-end" ? "bottom-end" : this.placement;
94
+ }
95
+
96
+ // fit-content takes precedence; the misuse is surfaced in className() below.
89
97
  private get effectiveWidth() {
90
98
  return this.isFitContent ? null : this.width;
91
99
  }
92
100
 
93
101
  private get effectiveFullWidth() {
102
+ if (this.widthMode === "full") {
103
+ return true;
104
+ }
94
105
  return this.isFitContent ? false : this.fullWidth;
95
106
  }
96
107
 
97
108
  private get className() {
98
109
  const fitContent = this.isFitContent;
110
+ if (fitContent && (this.width || this.fullWidth)) {
111
+ console.error(
112
+ '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.'
113
+ );
114
+ }
99
115
  return cx(
100
116
  this.isNested && "menu-nested",
101
117
  this.isNested &&
@@ -104,7 +120,8 @@ export default class Dropdown extends LightningElement {
104
120
  "menu-nested-2-col",
105
121
  this.offset && `menu-offset-${this.offset}`,
106
122
  this.areOptionsEmpty && "menu-hidden",
107
- fitContent && "menu-fit-content"
123
+ fitContent && "menu-fit-content",
124
+ this.widthMode === "fit-end" && "menu-fit-end"
108
125
  );
109
126
  }
110
127
 
@@ -25,8 +25,12 @@
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
+ padding: var(
29
+ --dx-c-dropdown-option-padding,
30
+ var(--dx-g-spacing-sm) var(--dx-g-spacing-lg)
31
+ );
29
32
  font-size: var(--dx-g-text-sm);
33
+ border-radius: var(--dx-c-dropdown-option-border-radius, 0);
30
34
  transition: var(--dx-g-transition-hue-1x);
31
35
  align-items: center;
32
36
  outline: none !important;
@@ -56,6 +60,10 @@
56
60
  display: flex;
57
61
  flex-direction: column;
58
62
  justify-content: center;
63
+ align-items: flex-start;
64
+ gap: var(--dx-g-spacing-sm);
65
+ flex: 1;
66
+ min-width: 0;
59
67
  font-family: var(--dx-g-font-sans);
60
68
  font-size: var(--dx-g-text-sm);
61
69
  text-align: left;
@@ -64,14 +72,29 @@
64
72
  .option-indented .option_details {
65
73
  align-items: center;
66
74
  flex-direction: row;
75
+ gap: 0;
67
76
  }
68
77
 
69
78
  .option_label {
70
- color: var(--dx-c-dropdown-option-label-color, var(--dx-g-blue-vibrant-50));
71
- display: flex;
79
+ color: var(--dx-c-dropdown-option-label-color, var(--dx-g-gray-10));
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
- font-weight: var(--dx-c-dropdown-option-font-weight, var(--dx-g-font-bold));
84
+ font-weight: var(
85
+ --dx-c-dropdown-option-font-weight,
86
+ var(--dx-g-font-normal)
87
+ );
74
88
  font-size: var(--dx-c-dropdown-option-font-size, var(--dx-g-text-base));
89
+ line-height: var(--dx-g-spacing-lg);
90
+
91
+ /* Opt-in truncation: consumers set --dx-c-dropdown-option-white-space:
92
+ nowrap to truncate labels with an ellipsis instead of wrapping.
93
+ Defaults preserve today's wrapping behavior. */
94
+ white-space: var(--dx-c-dropdown-option-white-space, normal);
95
+ overflow: var(--dx-c-dropdown-option-overflow, visible);
96
+ overflow-wrap: var(--dx-c-dropdown-option-overflow-wrap, normal);
97
+ text-overflow: var(--dx-c-dropdown-option-text-overflow, clip);
75
98
  }
76
99
 
77
100
  .option-indented .option_label {
@@ -88,13 +111,17 @@
88
111
 
89
112
  .option_description {
90
113
  color: var(--dx-g-gray-30);
91
- margin-top: var(--dx-g-spacing-xs);
92
114
  line-height: 18px;
93
115
  }
94
116
 
117
+ /* Only rendered when the dropdown sets show-option-icons. */
95
118
  .option_icon {
96
- margin-right: var(--dx-g-spacing-md);
119
+ --dx-c-icon-size: var(--dx-g-icon-size-md);
120
+ --dx-c-icon-color: var(--dx-g-gray-50);
121
+
122
+ order: 1;
97
123
  flex-shrink: 0;
124
+ margin-left: var(--dx-g-spacing-2xl);
98
125
  }
99
126
 
100
127
  .option-indented {
@@ -102,46 +129,19 @@
102
129
  padding-bottom: var(--dx-g-spacing-xs);
103
130
  }
104
131
 
132
+ /* fit-content (toolbar): one line per label, truncated with an ellipsis. */
105
133
  .option-fit-content {
106
134
  min-width: 0;
107
- padding: var(--dx-g-spacing-sm) var(--dx-g-spacing-lg);
108
- }
109
-
110
- .option-fit-content .option_details {
111
- align-items: flex-start;
112
- gap: var(--dx-g-spacing-sm);
113
- min-width: 0;
114
- flex: 1;
115
- }
116
-
117
- .option-fit-content .option_description {
118
- margin-top: 0;
119
135
  }
120
136
 
121
137
  .option-fit-content .option_label {
122
138
  display: block;
123
- color: var(--dx-g-gray-10);
124
- font-weight: var(--dx-g-font-normal);
125
- line-height: var(--dx-g-spacing-lg);
126
139
  white-space: nowrap;
127
140
  overflow: hidden;
128
141
  text-overflow: ellipsis;
129
142
  max-width: calc(100vw - var(--dx-g-spacing-lg) * 4);
130
143
  }
131
144
 
132
- .option-fit-content.option-active .option_label {
133
- color: var(--dx-g-blue-vibrant-50);
134
- }
135
-
136
- .option-fit-content .option_icon {
137
- --dx-c-icon-size: var(--dx-g-icon-size-md);
138
- --dx-c-icon-color: var(--dx-g-gray-50);
139
-
140
- order: 1;
141
- margin-right: 0;
142
- margin-left: var(--dx-g-spacing-2xl);
143
- }
144
-
145
145
  .option-indented:not([data-index="0"]) {
146
146
  --base-indentation: calc(var(--dx-g-spacing-md) + var(--shape-width));
147
147
  --shape-width: 8px;
@@ -14,8 +14,8 @@
14
14
  class="option_icon"
15
15
  symbol={option.iconSymbol}
16
16
  sprite={option.iconSprite}
17
- if:true={option.iconSymbol}
18
- size={optionIconSize}
17
+ lwc:if={renderIcon}
18
+ size="override"
19
19
  ></dx-icon>
20
20
  <div class="option_details">
21
21
  <div if:true={renderIndentedShape} class="indented-shape"></div>
@@ -44,8 +44,8 @@
44
44
  symbol={option.iconSymbol}
45
45
  sprite={option.iconSprite}
46
46
  color={option.iconColor}
47
- if:true={option.iconSymbol}
48
- size={optionIconSize}
47
+ lwc:if={renderIcon}
48
+ size="override"
49
49
  ></dx-icon>
50
50
  <div class="option_details">
51
51
  <div if:true={renderIndentedShape} class="indented-shape"></div>
@@ -4,8 +4,7 @@ import {
4
4
  OptionWithNested,
5
5
  AnalyticsPayload,
6
6
  DropdownVariant,
7
- DropdownWidthMode,
8
- IconSize
7
+ DropdownWidthMode
9
8
  } from "typings/custom";
10
9
  import { track } from "dxUtils/analytics";
11
10
 
@@ -19,6 +18,7 @@ export default class DropdownOption extends LightningElement {
19
18
  @api indexPosition: number = 0;
20
19
  @api variant: DropdownVariant = "base";
21
20
  @api widthMode: DropdownWidthMode = "fixed";
21
+ @api showIcon: boolean = false;
22
22
 
23
23
  @api focus() {
24
24
  const option = this.template.querySelector(".option");
@@ -41,8 +41,9 @@ export default class DropdownOption extends LightningElement {
41
41
  return this.variant === "indented" && this.indexPosition >= 1;
42
42
  }
43
43
 
44
- private get optionIconSize(): IconSize {
45
- return this.widthMode === "fit-content" ? "override" : "large";
44
+ // Icons are opt-in (right-aligned); without show-icon there is no icon or spacing.
45
+ private get renderIcon() {
46
+ return this.showIcon && !!this.option.iconSymbol;
46
47
  }
47
48
 
48
49
  private onClick(e: PointerEvent) {
@@ -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"