@salesforcedevs/dx-components 0.17.0 → 0.18.0

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 +40 -17
  13. package/src/modules/dx/sidebar/sidebar.html +58 -34
  14. package/src/modules/dx/sidebar/sidebar.stories.ts +54 -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 +158 -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 +8843 -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 +421 -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,10 +93,6 @@ 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
  }
@@ -152,7 +147,35 @@ dx-tree:not(:last-child) {
152
147
  white-space: nowrap;
153
148
  }
154
149
 
155
- dx-empty-state {
156
- --dx-c-empty-state-background: var(--sds-g-gray-2);
157
- --dx-c-empty-state-image-width: 400px;
150
+ .loading-skeleton {
151
+ margin-top: var(--dx-g-spacing-md);
152
+ }
153
+
154
+ .search-empty.container dx-empty-state {
155
+ display: block;
156
+ }
157
+
158
+ .user-searching.container .sidebar-content-tree {
159
+ display: none;
160
+ }
161
+
162
+ .show-tree.container .sidebar-content-search {
163
+ display: none;
164
+ }
165
+
166
+ .container.collapsed {
167
+ width: var(--dx-g-spacing-xl);
168
+ }
169
+
170
+ .container.collapsed .header-toggle-button {
171
+ transform: rotate(180deg);
172
+ }
173
+
174
+ .container.collapsed .sidebar-header,
175
+ .container.collapsed .sidebar-content {
176
+ display: none;
177
+ }
178
+
179
+ .container:hover .header-toggle-button {
180
+ --dx-c-button-opacity: 1;
158
181
  }
@@ -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",
@@ -33,18 +33,48 @@ const styles = (addStyles: string = "") => html`
33
33
  ${addStyles}
34
34
  }
35
35
 
36
+ dx-sidebar::part(container) {
37
+ height: 100vh;
38
+ }
39
+
36
40
  body.sb-main-padded.sb-show-main {
37
41
  padding: 0;
38
42
  }
43
+
44
+ #root-inner {
45
+ display: flex;
46
+ height: 100%;
47
+ }
48
+
49
+ doc-content {
50
+ padding: 20px;
51
+ }
39
52
  </style>
40
53
  `;
41
54
 
42
- export const Base = (args: any) => html`
43
- ${styles("--dx-c-sidebar-min-height: 100vh;")}
55
+ export const XMLDocs = (args: any) => html`
56
+ ${styles("--dx-c-sidebar-min-height: 100%;")}
57
+
58
+ <dx-sidebar
59
+ header="${args.header}"
60
+ trees="${JSON.stringify(args.trees)}"
61
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
62
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
63
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
64
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
65
+ ></dx-sidebar>
66
+ `;
67
+
68
+ export const SFDocs = (args: any) => html`
69
+ ${styles("--dx-c-sidebar-min-height: 100%;")}
44
70
 
45
71
  <dx-sidebar
46
72
  header="${args.header}"
47
73
  trees="${JSON.stringify(args.trees)}"
74
+ coveo-organization-id="${coveoSFDocsConfig.coveoOrganizationId}"
75
+ coveo-public-access-token="${coveoSFDocsConfig.coveoPublicAccessToken}"
76
+ coveo-search-hub="${coveoSFDocsConfig.coveoSearchHub}"
77
+ coveo-advanced-query-config="${coveoSFDocsConfig.coveoAdvancedQueryConfig}"
48
78
  ></dx-sidebar>
49
79
  `;
50
80
 
@@ -58,6 +88,10 @@ export const WidthAndMinHeightVariables = (args: any) => html`
58
88
  <dx-sidebar
59
89
  header="${args.header}"
60
90
  trees="${JSON.stringify(args.trees)}"
91
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
92
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
93
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
94
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
61
95
  ></dx-sidebar>
62
96
  `;
63
97
 
@@ -80,6 +114,10 @@ export const WidthDrivenByContainer = (args: any) => html`
80
114
  header="${args.header}"
81
115
  trees="${JSON.stringify(args.trees)}"
82
116
  value="${args.value}"
117
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
118
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
119
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
120
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
83
121
  ></dx-sidebar>
84
122
  </div>
85
123
  `;
@@ -92,6 +130,10 @@ export const WithBadges = (args: any) => html`
92
130
  header="${args.header}"
93
131
  trees="${JSON.stringify(SAMPLE_TREES_WITH_METHODS)}"
94
132
  value="${args.value}"
133
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
134
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
135
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
136
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
95
137
  ></dx-sidebar>
96
138
  </div>
97
139
  `;
@@ -109,6 +151,10 @@ export const PositionSticky = ({ header, trees, value }: any) => html`
109
151
  header="${header}"
110
152
  trees="${JSON.stringify(trees)}"
111
153
  value="${value}"
154
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
155
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
156
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
157
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
112
158
  ></dx-sidebar>
113
159
  `;
114
160
 
@@ -133,6 +179,10 @@ export const UseSlot = ({ header, trees, value }: any) => html`
133
179
  header="${header}"
134
180
  trees="${JSON.stringify(trees)}"
135
181
  value="${value}"
182
+ coveo-organization-id="${coveoXMLDocsConfig.coveoOrganizationId}"
183
+ coveo-public-access-token="${coveoXMLDocsConfig.coveoPublicAccessToken}"
184
+ coveo-search-hub="${coveoXMLDocsConfig.coveoSearchHub}"
185
+ coveo-advanced-query-config="${coveoXMLDocsConfig.coveoAdvancedQueryConfig}"
136
186
  >
137
187
  <span slot="header">I'm in the sidebar</span>
138
188
  </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 {