@salesforcedevs/dx-components 0.17.0 → 0.18.3

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 (43) hide show
  1. package/lwc.config.json +1 -0
  2. package/package.json +3 -3
  3. package/src/assets/svg/sidebar-loading.svg +34 -0
  4. package/src/modules/dx/header/__tests__/mockProps.ts +7 -1
  5. package/src/modules/dx/headerSearch/__tests__/headerSearch.test.ts +24 -39
  6. package/src/modules/dx/headerSearch/__tests__/mockProps.ts +2 -7
  7. package/src/modules/dx/headerSearch/headerSearch.ts +13 -19
  8. package/src/modules/dx/lazy/lazy.ts +0 -7
  9. package/src/modules/dx/sidebar/__tests__/mockSearchChangeEmptyEvent.ts +6 -0
  10. package/src/modules/dx/sidebar/__tests__/mockSearchChangeEvent.ts +527 -0
  11. package/src/modules/dx/sidebar/__tests__/sidebar.test.ts +210 -247
  12. package/src/modules/dx/sidebar/sidebar.css +68 -34
  13. package/src/modules/dx/sidebar/sidebar.html +58 -34
  14. package/src/modules/dx/sidebar/sidebar.stories.ts +50 -4
  15. package/src/modules/dx/sidebar/sidebar.ts +141 -73
  16. package/src/modules/dx/sidebarOld/__tests__/mockProps.ts +178 -0
  17. package/src/modules/dx/sidebarOld/__tests__/sidebarOld.test.ts +574 -0
  18. package/src/modules/dx/sidebarOld/sidebarOld.css +160 -0
  19. package/src/modules/dx/sidebarOld/sidebarOld.html +70 -0
  20. package/src/modules/dx/sidebarOld/sidebarOld.stories.ts +139 -0
  21. package/src/modules/dx/sidebarOld/sidebarOld.ts +187 -0
  22. package/src/modules/dx/sidebarSearch/__tests__/mockPreloadedState.ts +1678 -0
  23. package/src/modules/dx/sidebarSearch/__tests__/sidebarSearch.test.ts +113 -0
  24. package/src/modules/dx/sidebarSearch/sidebarSearch.css +3 -0
  25. package/src/modules/dx/sidebarSearch/sidebarSearch.html +26 -0
  26. package/src/modules/dx/sidebarSearch/sidebarSearch.ts +427 -0
  27. package/src/modules/dx/sidebarSearchResult/__tests__/sidebarSearchResult.test.ts +55 -0
  28. package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.css +36 -0
  29. package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.html +14 -0
  30. package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.ts +56 -0
  31. package/src/modules/dx/treeTile/__tests__/treeTile.test.ts +27 -38
  32. package/src/modules/dx/treeTile/treeTile.css +3 -19
  33. package/src/modules/dx/treeTile/treeTile.html +4 -1
  34. package/src/modules/dx/treeTile/treeTile.ts +2 -2
  35. package/src/modules/helpers/sidebar/sidebar.css +21 -0
  36. package/src/modules/utils/coveo/coveo.ts +69 -0
  37. package/src/modules/utils/highlight/__tests__/highlight.test.ts +108 -0
  38. package/src/modules/utils/highlight/__tests__/mockProps.ts +45 -0
  39. package/src/modules/utils/highlight/highlight.ts +2 -1
  40. package/src/modules/utils/jest/jest.ts +35 -0
  41. package/src/modules/utils/recentSearches/__tests__/recentSearches.test.ts +79 -0
  42. package/src/modules/utils/recentSearches/recentSearches.ts +57 -0
  43. package/src/modules/utils/tests/tests.ts +18 -11
@@ -13,6 +13,13 @@
13
13
  --dx-c-sidebar-vertical-padding: var(--dx-g-spacing-md);
14
14
  }
15
15
 
16
+ dx-empty-state {
17
+ --dx-c-empty-state-background: var(--sds-g-gray-2);
18
+ --dx-c-empty-state-image-width: 400px;
19
+
20
+ display: none;
21
+ }
22
+
16
23
  dx-tree:not(:last-child) {
17
24
  margin-bottom: var(--dx-g-spacing-sm);
18
25
  }
@@ -57,14 +64,6 @@ dx-tree:not(:last-child) {
57
64
  transition: var(--dx-g-transition-transform-2x);
58
65
  }
59
66
 
60
- .container.collapsed {
61
- width: var(--dx-g-spacing-xl);
62
- }
63
-
64
- .collapsed .header-toggle-button {
65
- transform: rotate(180deg);
66
- }
67
-
68
67
  .container {
69
68
  background-color: var(--dx-g-gray-95);
70
69
  display: flex;
@@ -74,12 +73,12 @@ dx-tree:not(:last-child) {
74
73
  }
75
74
 
76
75
  .container:not(.container-mobile) {
77
- --border-color: var(--dx-g-gray-95);
76
+ --border-color: transparent;
78
77
  --left-spacing: calc(
79
78
  var(--dx-c-sidebar-left-padding) - var(--dx-g-spacing-md)
80
79
  );
81
80
 
82
- border-right: 2px solid var(--border-color);
81
+ box-shadow: 2px 0 0 0 var(--border-color);
83
82
  min-height: var(--dx-c-sidebar-min-height);
84
83
  padding: var(--dx-c-sidebar-vertical-padding) 0
85
84
  var(--dx-c-sidebar-vertical-padding) var(--left-spacing);
@@ -94,14 +93,62 @@ dx-tree:not(:last-child) {
94
93
  --border-color: transparent;
95
94
  }
96
95
 
97
- .container:hover .header-toggle-button {
98
- --dx-c-button-opacity: 1;
99
- }
100
-
101
96
  .container.container-border-active {
102
97
  --border-color: var(--dx-c-sidebar-button-color);
103
98
  }
104
99
 
100
+ .sidebar-content {
101
+ overflow-y: auto;
102
+ padding-bottom: var(--dx-g-spacing-md);
103
+ }
104
+
105
+ .search {
106
+ padding-bottom: var(--dx-g-spacing-md);
107
+ }
108
+
109
+ .search-box {
110
+ --dx-c-input-width: 100%;
111
+ }
112
+
113
+ .truncate {
114
+ overflow: hidden;
115
+ text-overflow: ellipsis;
116
+ white-space: nowrap;
117
+ }
118
+
119
+ .loading-skeleton {
120
+ margin-top: var(--dx-g-spacing-md);
121
+ }
122
+
123
+ .search-empty.container dx-empty-state {
124
+ display: block;
125
+ }
126
+
127
+ .user-searching.container .sidebar-content-tree {
128
+ display: none;
129
+ }
130
+
131
+ .show-tree.container .sidebar-content-search {
132
+ display: none;
133
+ }
134
+
135
+ .container.collapsed {
136
+ width: var(--dx-g-spacing-xl);
137
+ }
138
+
139
+ .container.collapsed .header-toggle-button {
140
+ transform: rotate(180deg);
141
+ }
142
+
143
+ .container.collapsed .sidebar-header,
144
+ .container.collapsed .sidebar-content {
145
+ display: none;
146
+ }
147
+
148
+ .container:hover .header-toggle-button {
149
+ --dx-c-button-opacity: 1;
150
+ }
151
+
105
152
  .container.container-mobile {
106
153
  width: 100%;
107
154
  }
@@ -134,25 +181,12 @@ dx-tree:not(:last-child) {
134
181
  transform: rotateZ(-90deg);
135
182
  }
136
183
 
137
- .sidebar-content {
138
- overflow-y: auto;
139
- }
140
-
141
- .search {
142
- padding-bottom: var(--dx-g-spacing-md);
143
- }
144
-
145
- .search-box {
146
- --dx-c-input-width: 100%;
147
- }
184
+ @media (max-width: 768px) {
185
+ :host {
186
+ width: 100%;
187
+ }
148
188
 
149
- .truncate {
150
- overflow: hidden;
151
- text-overflow: ellipsis;
152
- white-space: nowrap;
153
- }
154
-
155
- dx-empty-state {
156
- --dx-c-empty-state-background: var(--sds-g-gray-2);
157
- --dx-c-empty-state-image-width: 400px;
189
+ .loading-skeleton {
190
+ width: 100%;
191
+ }
158
192
  }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class={containerClass}>
2
+ <div class={containerClass} part="container">
3
3
  <div
4
4
  if:true={mobile}
5
5
  class="header padding-horizontal"
@@ -27,44 +27,68 @@
27
27
  onmouseenter={onToggleMouseEnter}
28
28
  onmouseleave={onToggleMouseLeave}
29
29
  ></dx-button>
30
-
31
- <template if:true={expanded}>
32
- <div class="padding-horizontal">
33
- <div class="header" if:false={mobile}>
34
- <h2 class="dx-text-heading-4 header-title">{header}</h2>
35
- <slot name="header"></slot>
36
- </div>
37
- <div class="search">
38
- <dx-input
39
- class="search-box"
40
- type="search"
41
- placeholder="Search this list..."
42
- role="search"
43
- aria-label="Content Search"
44
- icon-symbol="search"
45
- size="small"
46
- value={searchText}
47
- onchange={onSearchChange}
48
- shortcut-key="j"
49
- ></dx-input>
50
- </div>
30
+ <div class="sidebar-header padding-horizontal">
31
+ <div class="header" if:false={mobile}>
32
+ <h2 class="dx-text-heading-4 header-title">{header}</h2>
33
+ <slot name="header"></slot>
51
34
  </div>
52
- <div class="sidebar-content" if:true={anyResultMatch}>
53
- <dx-tree
54
- for:each={filteredTrees}
55
- for:item="tree"
56
- key={tree.key}
57
- tree-root={tree.tree}
58
- onselect={onSelect}
59
- value={value}
60
- ></dx-tree>
35
+ <div class="search">
36
+ <dx-sidebar-search
37
+ onchange={onSearchChange}
38
+ onloading={onSearchLoading}
39
+ coveo-organization-id={coveoOrganizationId}
40
+ coveo-public-access-token={coveoPublicAccessToken}
41
+ coveo-search-hub={coveoSearchHub}
42
+ coveo-advanced-query-config={coveoAdvancedQueryConfig}
43
+ ></dx-sidebar-search>
61
44
  </div>
45
+ <h2
46
+ class="results-heading dx-text-heading-6"
47
+ if:true={showResultsHeading}
48
+ >
49
+ Results
50
+ </h2>
51
+ </div>
52
+ <div
53
+ class="sidebar-content sidebar-content-search"
54
+ onscroll={onSidebarSearchContentScroll}
55
+ >
56
+ <img
57
+ if:true={isSearchLoading}
58
+ class="loading-skeleton padding-horizontal"
59
+ src="/assets/svg/sidebar-loading.svg"
60
+ alt="loading"
61
+ />
62
+ <template
63
+ if:true={showSearchResults}
64
+ for:each={searchResults}
65
+ for:item="result"
66
+ >
67
+ <dx-sidebar-search-result
68
+ description-highlights={result.excerptHighlights}
69
+ description={result.excerpt}
70
+ href={result.href}
71
+ key={result.uniqueId}
72
+ selected={result.selected}
73
+ title-highlights={result.titleHighlights}
74
+ title={result.title}
75
+ ></dx-sidebar-search-result>
76
+ </template>
62
77
  <dx-empty-state
63
78
  class="padding-horizontal"
64
- size="small"
65
79
  title="No Results"
66
- if:false={anyResultMatch}
80
+ size="small"
67
81
  ></dx-empty-state>
68
- </template>
82
+ </div>
83
+ <div class="sidebar-content sidebar-content-tree">
84
+ <dx-tree
85
+ for:each={trees}
86
+ for:item="tree"
87
+ key={tree.key}
88
+ tree-root={tree.tree}
89
+ onselect={onSelect}
90
+ value={value}
91
+ ></dx-tree>
92
+ </div>
69
93
  </div>
70
94
  </template>
@@ -1,6 +1,6 @@
1
- import { SAMPLE_TREES, SAMPLE_TREES_WITH_METHODS } from "./__tests__/mockProps";
2
-
3
1
  import { html } from "lit-html";
2
+ import { coveoXMLDocsConfig, coveoSFDocsConfig } from "../../utils/coveo/coveo";
3
+ import { SAMPLE_TREES, SAMPLE_TREES_WITH_METHODS } from "./__tests__/mockProps";
4
4
 
5
5
  export default {
6
6
  title: "dx/dx-sidebar",
@@ -36,15 +36,41 @@ const styles = (addStyles: string = "") => html`
36
36
  body.sb-main-padded.sb-show-main {
37
37
  padding: 0;
38
38
  }
39
+
40
+ #root-inner {
41
+ display: flex;
42
+ height: 100%;
43
+ }
44
+
45
+ doc-content {
46
+ padding: 20px;
47
+ }
39
48
  </style>
40
49
  `;
41
50
 
42
- export const Base = (args: any) => html`
43
- ${styles("--dx-c-sidebar-min-height: 100vh;")}
51
+ export const XMLDocs = (args: any) => html`
52
+ ${styles("--dx-c-sidebar-min-height: 100%;")}
53
+
54
+ <dx-sidebar
55
+ header="${args.header}"
56
+ trees="${JSON.stringify(args.trees)}"
57
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
58
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
59
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
60
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
61
+ ></dx-sidebar>
62
+ `;
63
+
64
+ export const SFDocs = (args: any) => html`
65
+ ${styles("--dx-c-sidebar-min-height: 100%;")}
44
66
 
45
67
  <dx-sidebar
46
68
  header="${args.header}"
47
69
  trees="${JSON.stringify(args.trees)}"
70
+ coveo-organization-id="${coveoSFDocsConfig.coveoOrganizationId}"
71
+ coveo-public-access-token="${coveoSFDocsConfig.coveoPublicAccessToken}"
72
+ coveo-search-hub="${coveoSFDocsConfig.coveoSearchHub}"
73
+ coveo-advanced-query-config="${coveoSFDocsConfig.coveoAdvancedQueryConfig}"
48
74
  ></dx-sidebar>
49
75
  `;
50
76
 
@@ -58,6 +84,10 @@ export const WidthAndMinHeightVariables = (args: any) => html`
58
84
  <dx-sidebar
59
85
  header="${args.header}"
60
86
  trees="${JSON.stringify(args.trees)}"
87
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
88
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
89
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
90
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
61
91
  ></dx-sidebar>
62
92
  `;
63
93
 
@@ -80,6 +110,10 @@ export const WidthDrivenByContainer = (args: any) => html`
80
110
  header="${args.header}"
81
111
  trees="${JSON.stringify(args.trees)}"
82
112
  value="${args.value}"
113
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
114
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
115
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
116
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
83
117
  ></dx-sidebar>
84
118
  </div>
85
119
  `;
@@ -92,6 +126,10 @@ export const WithBadges = (args: any) => html`
92
126
  header="${args.header}"
93
127
  trees="${JSON.stringify(SAMPLE_TREES_WITH_METHODS)}"
94
128
  value="${args.value}"
129
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
130
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
131
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
132
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
95
133
  ></dx-sidebar>
96
134
  </div>
97
135
  `;
@@ -109,6 +147,10 @@ export const PositionSticky = ({ header, trees, value }: any) => html`
109
147
  header="${header}"
110
148
  trees="${JSON.stringify(trees)}"
111
149
  value="${value}"
150
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
151
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
152
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
153
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
112
154
  ></dx-sidebar>
113
155
  `;
114
156
 
@@ -133,6 +175,10 @@ export const UseSlot = ({ header, trees, value }: any) => html`
133
175
  header="${header}"
134
176
  trees="${JSON.stringify(trees)}"
135
177
  value="${value}"
178
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
179
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
180
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
181
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
136
182
  >
137
183
  <span slot="header">I'm in the sidebar</span>
138
184
  </dx-sidebar>
@@ -1,15 +1,23 @@
1
1
  import cx from "classnames";
2
- import { track } from "dx/instrumentation";
3
- import { LightningElement, api } from "lwc";
4
-
5
- import { TreeNode, SidebarGtmAction } from "typings/custom";
2
+ import { track as trackAnalytics } from "dx/instrumentation";
3
+ import { LightningElement, api, track } from "lwc";
4
+ import {
5
+ TreeNode,
6
+ SidebarGtmAction,
7
+ SidebarSearchResult
8
+ } from "typings/custom";
9
+ import { getSidebarSearchParams } from "utils/coveo";
6
10
  import { toJson } from "utils/normalizers";
11
+ import SidebarSearch from "dx/sidebarSearch";
7
12
 
8
- const WAIT_TIME = 500;
9
13
  const MOBILE_SIZE_MATCH = "768px";
10
14
  const TOGGLE_BUTTON_LABEL = "Toggle Sidebar";
11
15
 
12
16
  export default class Sidebar extends LightningElement {
17
+ @api coveoOrganizationId!: string;
18
+ @api coveoPublicAccessToken!: string;
19
+ @api coveoSearchHub!: string;
20
+ @api coveoAdvancedQueryConfig!: string;
13
21
  @api header: string = "";
14
22
 
15
23
  @api
@@ -27,38 +35,94 @@ export default class Sidebar extends LightningElement {
27
35
  }
28
36
 
29
37
  set trees(value: string | Array<TreeNode>) {
30
- let parsedValue = toJson(value);
38
+ let parsedValue = toJson(value) || [];
31
39
  if (parsedValue && !Array.isArray(parsedValue)) {
32
40
  parsedValue = [parsedValue];
33
41
  }
34
42
 
35
- this._trees = parsedValue || [];
43
+ parsedValue.forEach((tree: TreeNode) => this.assignValueToLabel(tree));
44
+
45
+ this._trees = parsedValue.map((tree: TreeNode) => ({
46
+ key: this.makeKey(),
47
+ tree
48
+ }));
49
+ }
36
50
 
37
- this._trees.forEach((tree) => this.assignValueToLabel(tree));
38
- this.assignFilteredTrees();
51
+ @api
52
+ setInputValue(searchTerm: string) {
53
+ (
54
+ this.template.querySelector(
55
+ "dx-sidebar-search"
56
+ ) as unknown as SidebarSearch
57
+ )?.setInputValue(searchTerm);
39
58
  }
40
59
 
41
60
  private expanded: boolean = true;
42
61
  private _toggleHovered: boolean = false;
43
- private _searchTimeout?: number;
44
62
  private _value?: string = undefined;
63
+
64
+ @track
45
65
  private _trees!: Array<TreeNode>;
46
66
 
47
- private filteredTrees: Array<{ key: string; tree: TreeNode }> = [];
48
67
  private mobile: boolean = true;
49
- private searchText: string = "";
50
68
  private matchMedia!: MediaQueryList;
51
69
  private valueToLabel: { [key: string]: string } = {};
52
70
 
53
- private get anyResultMatch() {
54
- return this.filteredTrees.length > 0;
71
+ private isSearchLoading: boolean = false;
72
+ private searchValue: string | null = null;
73
+ private searchResults: SidebarSearchResult[] = [];
74
+ private scrollToSelectedSearchResult: boolean = false;
75
+ private selectedSearchResultIndex: number = -1;
76
+ private requestedFetchMoreResults: boolean = false;
77
+
78
+ private get areResultsEmpty(): boolean {
79
+ return (
80
+ (!this.searchResults ||
81
+ (this.searchResults && this.searchResults.length < 1)) &&
82
+ !this.isSearchLoading
83
+ );
84
+ }
85
+
86
+ private get isSearchEmpty(): boolean {
87
+ return this.isUserSearching && this.areResultsEmpty;
88
+ }
89
+
90
+ private get showResultsHeading(): boolean {
91
+ return this.isUserSearching && !this.areResultsEmpty;
92
+ }
93
+
94
+ private get isUserSearching(): boolean {
95
+ return !!this.searchValue || this.isSearchLoading;
96
+ }
97
+
98
+ private get showSearchResults(): boolean {
99
+ return (
100
+ !!this.searchValue && !this.isSearchLoading && !this.areResultsEmpty
101
+ );
102
+ }
103
+
104
+ private get isNearBottomOfSearchResults(): boolean {
105
+ const scrollArea = this.template.querySelector(
106
+ ".sidebar-content-search"
107
+ );
108
+ if (!scrollArea) {
109
+ return false;
110
+ }
111
+
112
+ const { scrollHeight, scrollTop } = scrollArea;
113
+ const { height } = scrollArea?.getBoundingClientRect();
114
+
115
+ return scrollHeight - scrollTop - height < 300;
55
116
  }
56
117
 
57
118
  private get containerClass() {
58
119
  return cx("container", {
59
120
  collapsed: !this.expanded,
60
121
  "container-border-active": this._toggleHovered,
61
- "container-mobile": this.mobile
122
+ "container-mobile": this.mobile,
123
+ "user-searching": this.isUserSearching,
124
+ "show-tree": !this.isUserSearching,
125
+ "search-empty": this.isSearchEmpty
62
126
  });
63
127
  }
64
128
 
@@ -70,6 +134,19 @@ export default class Sidebar extends LightningElement {
70
134
  );
71
135
  }
72
136
 
137
+ constructor() {
138
+ super();
139
+
140
+ if (getSidebarSearchParams()) {
141
+ this.isSearchLoading = true;
142
+ this.scrollToSelectedSearchResult = true;
143
+ }
144
+ }
145
+
146
+ renderedCallback() {
147
+ this.initializeSearchScrollPosition();
148
+ }
149
+
73
150
  connectedCallback() {
74
151
  this.matchMedia = window.matchMedia(
75
152
  `(max-width: ${MOBILE_SIZE_MATCH})`
@@ -97,7 +174,7 @@ export default class Sidebar extends LightningElement {
97
174
  const sideNavAction: SidebarGtmAction = this.expanded
98
175
  ? "expand"
99
176
  : "collapse";
100
- track(event.currentTarget!, "custEv_leftNavLinkClick", {
177
+ trackAnalytics(event.currentTarget!, "custEv_leftNavLinkClick", {
101
178
  sideNavAction,
102
179
  clickText: TOGGLE_BUTTON_LABEL
103
180
  });
@@ -111,71 +188,62 @@ export default class Sidebar extends LightningElement {
111
188
  this._toggleHovered = false;
112
189
  }
113
190
 
114
- /*
115
- * Debouncing this method: Do not apply searchText filter as long as this
116
- * function is being called to avoid running the recursivity logic very often.
117
- **/
118
- private onSearchChange(event: CustomEvent) {
119
- this.searchText = event.detail;
120
- window.clearTimeout(this._searchTimeout);
121
- this._searchTimeout = window.setTimeout(() => {
122
- this.assignFilteredTrees();
123
- this.dispatchEvent(
124
- new CustomEvent("highlightedtermchange", {
125
- detail: this.searchText,
126
- composed: true,
127
- bubbles: true
128
- })
129
- );
130
- }, WAIT_TIME);
191
+ private makeKey(): string {
192
+ return Math.random().toString(36).substring(7);
131
193
  }
132
194
 
133
- private assignFilteredTrees() {
134
- this.filteredTrees = this._trees
135
- .map((singleTree) => {
136
- return {
137
- key: this.makeKey(),
138
- tree: this.searchText
139
- ? this.findMatch(
140
- new RegExp(this.searchText.toLowerCase().trim()),
141
- singleTree
142
- )!
143
- : singleTree
144
- };
145
- })
146
- .filter((filteredTree) => filteredTree.tree);
195
+ private onSearchChange(e: CustomEvent) {
196
+ this.requestedFetchMoreResults = false;
197
+ this.searchResults = e.detail.results;
198
+ this.searchValue = e.detail.value;
147
199
  }
148
200
 
149
- private makeKey(): string {
150
- return Math.random().toString(36).substring(7);
201
+ private onSearchLoading(e: CustomEvent) {
202
+ if (!e.detail.loading && this.scrollToSelectedSearchResult) {
203
+ this.selectedSearchResultIndex = this.searchResults.findIndex(
204
+ (r) => r.selected
205
+ );
206
+ }
207
+ this.isSearchLoading = e.detail;
151
208
  }
152
209
 
153
- private findMatch(
154
- valuePattern: RegExp,
155
- node: TreeNode
156
- ): TreeNode | undefined {
157
- if (valuePattern.test(node.label.toLowerCase())) {
158
- return { ...node, isExpanded: true };
210
+ private onSidebarSearchContentScroll() {
211
+ if (this.isSearchLoading) {
212
+ return;
159
213
  }
160
214
 
161
- const filteredChildren = this.filterChildren(
162
- valuePattern,
163
- node.children
164
- );
165
- return filteredChildren.length > 0
166
- ? { ...node, children: filteredChildren, isExpanded: true }
167
- : undefined;
168
- }
169
-
170
- private filterChildren(
171
- valuePattern: RegExp,
172
- children?: Array<TreeNode>
173
- ): Array<TreeNode> {
174
- return !children || children.length === 0
175
- ? []
176
- : (children
177
- .map((child) => this.findMatch(valuePattern, child))
178
- .filter((child) => child) as Array<TreeNode>);
215
+ const search = this.template.querySelector(
216
+ "dx-sidebar-search"
217
+ ) as unknown as SidebarSearch;
218
+
219
+ if (
220
+ this.isNearBottomOfSearchResults &&
221
+ search &&
222
+ !this.requestedFetchMoreResults
223
+ ) {
224
+ this.requestedFetchMoreResults = true;
225
+ search.fetchMoreResults();
226
+ }
227
+ }
228
+
229
+ private initializeSearchScrollPosition() {
230
+ if (
231
+ this.scrollToSelectedSearchResult &&
232
+ this.selectedSearchResultIndex >= 0
233
+ ) {
234
+ const selectedResult = this.template.querySelector(
235
+ `dx-sidebar-search-result:nth-child(${
236
+ this.selectedSearchResultIndex + 1
237
+ })`
238
+ );
239
+ if (!selectedResult || !selectedResult.parentNode) {
240
+ return;
241
+ }
242
+ (selectedResult.parentNode as HTMLElement).scrollTop =
243
+ selectedResult.offsetTop -
244
+ (selectedResult.parentNode as HTMLElement).offsetTop;
245
+ this.scrollToSelectedSearchResult = false;
246
+ }
179
247
  }
180
248
 
181
249
  private assignValueToLabel(node: TreeNode): void {