@salesforcedevs/dx-components 0.10.0 → 0.10.1-alpha.100

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 (88) hide show
  1. package/lwc.config.json +3 -3
  2. package/package.json +3 -4
  3. package/src/.DS_Store +0 -0
  4. package/src/assets/svg/docs-empty-state-small.svg +72 -0
  5. package/src/assets/svg/sidebar-loading.svg +34 -0
  6. package/src/modules/.DS_Store +0 -0
  7. package/src/modules/dx/.DS_Store +0 -0
  8. package/src/modules/dx/cardGrid/__tests__/cardMockProps.ts +78 -148
  9. package/src/modules/dx/cardGrid/cardGrid.css +0 -32
  10. package/src/modules/dx/cardGrid/cardGrid.html +5 -11
  11. package/src/modules/dx/codeBlock/__tests__/codeBlock.test.ts +47 -5
  12. package/src/modules/dx/codeBlock/codeBlock.stories.ts +2 -69
  13. package/src/modules/dx/codeBlock/codeBlock.ts +43 -22
  14. package/src/modules/dx/codeBlock/darkTheme.css +0 -1
  15. package/src/modules/dx/codeBlock/lightTheme.css +0 -1
  16. package/src/modules/dx/emptyState/__tests__/emptyState.test.ts +29 -10
  17. package/src/modules/dx/emptyState/__tests__/mockProps.ts +1 -3
  18. package/src/modules/dx/emptyState/emptyState.css +45 -13
  19. package/src/modules/dx/emptyState/emptyState.html +6 -4
  20. package/src/modules/dx/emptyState/emptyState.stories.ts +18 -38
  21. package/src/modules/dx/emptyState/emptyState.ts +11 -20
  22. package/src/modules/dx/footer/links.ts +3 -5
  23. package/src/modules/dx/header/__tests__/mockProps.ts +7 -1
  24. package/src/modules/dx/headerNav/__tests__/headerNav.test.ts +2 -4
  25. package/src/modules/dx/headerNav/headerNav.html +8 -6
  26. package/src/modules/dx/headerNav/headerNav.ts +2 -2
  27. package/src/modules/dx/headerSearch/headerSearch.html +2 -4
  28. package/src/modules/dx/headerSearch/headerSearch.ts +19 -25
  29. package/src/modules/dx/newsletterForm/newsletterForm.html +5 -3
  30. package/src/modules/dx/popover/popover.ts +11 -7
  31. package/src/modules/dx/searchResults/coveo.css +314 -111
  32. package/src/modules/dx/sidebar/__tests__/sidebar.test.ts +0 -70
  33. package/src/modules/dx/sidebar/sidebar.css +42 -23
  34. package/src/modules/dx/sidebar/sidebar.html +62 -40
  35. package/src/modules/dx/sidebar/sidebar.stories.ts +54 -4
  36. package/src/modules/dx/sidebar/sidebar.ts +126 -66
  37. package/src/modules/dx/sidebarSearch/__tests__/preloadedState.ts +3459 -0
  38. package/src/modules/dx/sidebarSearch/__tests__/sidebarSearch.test.ts +44 -0
  39. package/src/modules/dx/sidebarSearch/sidebarSearch.css +3 -0
  40. package/src/modules/dx/sidebarSearch/sidebarSearch.html +26 -0
  41. package/src/modules/dx/sidebarSearch/sidebarSearch.ts +358 -0
  42. package/src/modules/dx/sidebarSearch/utils.ts +2 -0
  43. package/src/modules/dx/sidebarSearchResult/__tests__/sidebarSearchResult.test.ts +55 -0
  44. package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.css +36 -0
  45. package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.html +14 -0
  46. package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.ts +56 -0
  47. package/src/modules/dx/spinner/__tests__/spinner.test.ts +37 -0
  48. package/src/modules/dx/spinner/spinner.css +750 -0
  49. package/src/modules/dx/spinner/spinner.html +6 -0
  50. package/src/modules/dx/spinner/spinner.stories.ts +69 -0
  51. package/src/modules/dx/spinner/spinner.ts +20 -0
  52. package/src/modules/dx/tab/__tests__/tab.test.ts +112 -54
  53. package/src/modules/dx/tab/tab.css +91 -16
  54. package/src/modules/dx/tab/tab.html +10 -12
  55. package/src/modules/dx/tab/tab.stories.ts +57 -0
  56. package/src/modules/dx/tab/tab.ts +35 -28
  57. package/src/modules/dx/tooltip/tippy.css +1 -0
  58. package/src/modules/dx/treeTile/__tests__/treeTile.test.ts +1 -1
  59. package/src/modules/dx/treeTile/treeTile.css +3 -19
  60. package/src/modules/dx/treeTile/treeTile.html +4 -1
  61. package/src/modules/dx/treeTile/treeTile.ts +2 -2
  62. package/src/modules/helpers/sidebar/sidebar.css +21 -0
  63. package/src/modules/helpers/table/table.css +23 -7
  64. package/src/modules/utils/coveo/coveo.ts +60 -0
  65. package/src/modules/utils/highlight/highlight.ts +31 -0
  66. package/src/modules/utils/normalizers/normalizers.ts +3 -0
  67. package/src/modules/utils/recentSearches/__tests__/recentSearches.test.ts +79 -0
  68. package/src/modules/utils/recentSearches/recentSearches.ts +57 -0
  69. package/src/modules/utils/regexps/regexps.ts +5 -0
  70. package/LICENSE +0 -12
  71. package/src/modules/dx/headerNavOption/__tests__/headerNavOption.test.ts +0 -113
  72. package/src/modules/dx/headerNavOption/headerNavOption.css +0 -91
  73. package/src/modules/dx/headerNavOption/headerNavOption.html +0 -9
  74. package/src/modules/dx/headerNavOption/headerNavOption.ts +0 -31
  75. package/src/modules/dx/headerSearch/__tests__/mockProps.ts +0 -11
  76. package/src/modules/dx/tab/__tests__/mockProps.ts +0 -4
  77. package/src/modules/dx/tabPanel/__tests__/mockProps.ts +0 -4
  78. package/src/modules/dx/tabPanel/__tests__/tabPanel.test.ts +0 -46
  79. package/src/modules/dx/tabPanel/tabPanel.css +0 -9
  80. package/src/modules/dx/tabPanel/tabPanel.html +0 -13
  81. package/src/modules/dx/tabPanel/tabPanel.ts +0 -27
  82. package/src/modules/dx/tabs/__tests__/mockProps.ts +0 -3
  83. package/src/modules/dx/tabs/__tests__/tabs.test.ts +0 -47
  84. package/src/modules/dx/tabs/tabs.css +0 -7
  85. package/src/modules/dx/tabs/tabs.html +0 -21
  86. package/src/modules/dx/tabs/tabs.stories.ts +0 -74
  87. package/src/modules/dx/tabs/tabs.ts +0 -112
  88. package/src/modules/dx/tabs/uniqueId.ts +0 -6
@@ -3,7 +3,6 @@ import Input from "dx/input";
3
3
  import { track } from "dx/instrumentation";
4
4
  import Sidebar from "dx/sidebar";
5
5
  import Tree from "dx/tree";
6
- import { TreeNode } from "typings/custom";
7
6
  import { createMediaMock } from "utils/jest";
8
7
  import { createRenderComponent } from "utils/tests";
9
8
  import { SAMPLE_TREES } from "./mockProps";
@@ -536,74 +535,5 @@ describe(TAG, () => {
536
535
  });
537
536
  });
538
537
  });
539
-
540
- describe("search", () => {
541
- beforeAll(() => {
542
- jest.useFakeTimers();
543
- });
544
-
545
- it("should filter trees element label based on the search text value", () => {
546
- const testSearchText = " token ";
547
- const component = renderComponent({
548
- header: "Cool Title",
549
- trees: SAMPLE_TREES
550
- });
551
-
552
- const searchBox: Input = component.shadowRoot.querySelector(
553
- "dx-input"
554
- );
555
- searchBox.dispatchEvent(
556
- new CustomEvent("change", {
557
- detail: testSearchText
558
- })
559
- );
560
-
561
- jest.runAllTimers();
562
-
563
- return Promise.resolve().then(() => {
564
- expect(searchBox.value).toBe(testSearchText);
565
-
566
- const trees: Array<Tree> = component.shadowRoot.querySelectorAll(
567
- "dx-tree"
568
- );
569
- expect(trees).toHaveLength(1);
570
- const treeNode = trees[0].treeRoot as TreeNode;
571
- expect(treeNode.name).toBe(SAMPLE_TREES[0].name);
572
- expect(treeNode.isExpanded).toBe(true);
573
- expect(treeNode.children).toHaveLength(1);
574
- expect(treeNode.children![0].name).toMatch(
575
- testSearchText.trim()
576
- );
577
- });
578
- });
579
-
580
- it("should not display anything if search text does not match", () => {
581
- const testSearchText = "this does not exist";
582
- const component = renderComponent({
583
- header: "Cool Title",
584
- trees: SAMPLE_TREES
585
- });
586
-
587
- const searchBox: Input = component.shadowRoot.querySelector(
588
- "dx-input"
589
- );
590
- searchBox.dispatchEvent(
591
- new CustomEvent("change", {
592
- detail: testSearchText
593
- })
594
- );
595
-
596
- jest.runAllTimers();
597
-
598
- return Promise.resolve().then(() => {
599
- expect(searchBox.value).toBe(testSearchText);
600
-
601
- const tree: Tree = component.shadowRoot.querySelector(
602
- "dx-tree"
603
- );
604
- expect(tree).toBeNull();
605
- });
606
- });
607
- });
608
538
  });
609
539
  });
@@ -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
  }
@@ -146,17 +141,41 @@ dx-tree:not(:last-child) {
146
141
  --dx-c-input-width: 100%;
147
142
  }
148
143
 
149
- .no-result {
150
- flex-grow: 1;
151
- display: flex;
152
- }
153
-
154
- .no-result dx-empty-state {
155
- margin: auto;
156
- }
157
-
158
144
  .truncate {
159
145
  overflow: hidden;
160
146
  text-overflow: ellipsis;
161
147
  white-space: nowrap;
162
148
  }
149
+
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;
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,46 +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>
51
- </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>
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>
61
34
  </div>
62
- <div class="no-result padding-horizontal" if:false={anyResultMatch}>
63
- <dx-empty-state
64
- title="No matches"
65
- subtitle="Try searching for different words"
66
- img-src="/assets/svg/lake-mountain.svg"
67
- img-alt="No matches"
68
- ></dx-empty-state>
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>
69
44
  </div>
70
- </template>
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>
77
+ <dx-empty-state
78
+ class="padding-horizontal"
79
+ title="No Results"
80
+ size="small"
81
+ ></dx-empty-state>
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>
71
93
  </div>
72
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,19 @@
1
1
  import cx from "classnames";
2
- import { track } from "dx/instrumentation";
3
- import { LightningElement, api } from "lwc";
4
-
2
+ import { track as trackAnalytics } from "dx/instrumentation";
3
+ import { LightningElement, api, track } from "lwc";
4
+ import { Result } from "@coveo/headless";
5
5
  import { TreeNode, SidebarGtmAction } from "typings/custom";
6
+ import { getSidebarSearchParams } from "utils/coveo";
6
7
  import { toJson } from "utils/normalizers";
7
8
 
8
- const WAIT_TIME = 500;
9
9
  const MOBILE_SIZE_MATCH = "768px";
10
10
  const TOGGLE_BUTTON_LABEL = "Toggle Sidebar";
11
11
 
12
12
  export default class Sidebar extends LightningElement {
13
+ @api coveoOrganizationId!: string;
14
+ @api coveoPublicAccessToken!: string;
15
+ @api coveoSearchHub!: string;
16
+ @api coveoAdvancedQueryConfig!: string;
13
17
  @api header: string = "";
14
18
 
15
19
  @api
@@ -27,38 +31,85 @@ export default class Sidebar extends LightningElement {
27
31
  }
28
32
 
29
33
  set trees(value: string | Array<TreeNode>) {
30
- let parsedValue = toJson(value);
34
+ let parsedValue = toJson(value) || [];
31
35
  if (parsedValue && !Array.isArray(parsedValue)) {
32
36
  parsedValue = [parsedValue];
33
37
  }
34
38
 
35
- this._trees = parsedValue || [];
39
+ parsedValue.forEach((tree: TreeNode) => this.assignValueToLabel(tree));
36
40
 
37
- this._trees.forEach((tree) => this.assignValueToLabel(tree));
38
- this.assignFilteredTrees();
41
+ this._trees = parsedValue.map((tree: TreeNode) => ({
42
+ key: this.makeKey(),
43
+ tree
44
+ }));
39
45
  }
40
46
 
41
47
  private expanded: boolean = true;
42
48
  private _toggleHovered: boolean = false;
43
- private _searchTimeout?: number;
44
49
  private _value?: string = undefined;
50
+
51
+ @track
45
52
  private _trees!: Array<TreeNode>;
46
53
 
47
- private filteredTrees: Array<{ key: string; tree: TreeNode }> = [];
48
54
  private mobile: boolean = true;
49
- private searchText: string = "";
50
55
  private matchMedia!: MediaQueryList;
51
56
  private valueToLabel: { [key: string]: string } = {};
52
57
 
53
- private get anyResultMatch() {
54
- return this.filteredTrees.length > 0;
58
+ private isSearchLoading: boolean = true;
59
+ private searchValue: string | null = null;
60
+ private searchResults: Result[] = [];
61
+ private scrollToSelectedSearchResult: boolean = false;
62
+ private selectedSearchResultIndex: number = -1;
63
+ private requestedFetchMoreResults: boolean = false;
64
+
65
+ private get areResultsEmpty(): boolean {
66
+ return (
67
+ (!this.searchResults ||
68
+ (this.searchResults && this.searchResults.length < 1)) &&
69
+ !this.isSearchLoading
70
+ );
71
+ }
72
+
73
+ private get isSearchEmpty(): boolean {
74
+ return this.isUserSearching && this.areResultsEmpty;
75
+ }
76
+
77
+ private get showResultsHeading(): boolean {
78
+ return this.isUserSearching && !this.areResultsEmpty;
79
+ }
80
+
81
+ private get isUserSearching(): boolean {
82
+ return !!this.searchValue || this.isSearchLoading;
83
+ }
84
+
85
+ private get showSearchResults(): boolean {
86
+ return (
87
+ !!this.searchValue && !this.isSearchLoading && !this.areResultsEmpty
88
+ );
89
+ }
90
+
91
+ private get isNearBottomOfSearchResults(): boolean {
92
+ const scrollArea = this.template.querySelector(
93
+ ".sidebar-content-search"
94
+ );
95
+ if (!scrollArea) {
96
+ return false;
97
+ }
98
+
99
+ const { scrollHeight, scrollTop } = scrollArea;
100
+ const { height } = scrollArea?.getBoundingClientRect();
101
+
102
+ return scrollHeight - scrollTop - height < 300;
55
103
  }
56
104
 
57
105
  private get containerClass() {
58
106
  return cx("container", {
59
107
  collapsed: !this.expanded,
60
108
  "container-border-active": this._toggleHovered,
61
- "container-mobile": this.mobile
109
+ "container-mobile": this.mobile,
110
+ "user-searching": this.isUserSearching,
111
+ "show-tree": !this.isUserSearching,
112
+ "search-empty": this.isSearchEmpty
62
113
  });
63
114
  }
64
115
 
@@ -70,6 +121,19 @@ export default class Sidebar extends LightningElement {
70
121
  );
71
122
  }
72
123
 
124
+ constructor() {
125
+ super();
126
+
127
+ if (getSidebarSearchParams()) {
128
+ this.isSearchLoading = true;
129
+ this.scrollToSelectedSearchResult = true;
130
+ }
131
+ }
132
+
133
+ renderedCallback() {
134
+ this.initializeSearchScrollPosition();
135
+ }
136
+
73
137
  connectedCallback() {
74
138
  this.matchMedia = window.matchMedia(
75
139
  `(max-width: ${MOBILE_SIZE_MATCH})`
@@ -97,7 +161,7 @@ export default class Sidebar extends LightningElement {
97
161
  const sideNavAction: SidebarGtmAction = this.expanded
98
162
  ? "expand"
99
163
  : "collapse";
100
- track(event.currentTarget!, "custEv_leftNavLinkClick", {
164
+ trackAnalytics(event.currentTarget!, "custEv_leftNavLinkClick", {
101
165
  sideNavAction,
102
166
  clickText: TOGGLE_BUTTON_LABEL
103
167
  });
@@ -111,66 +175,62 @@ export default class Sidebar extends LightningElement {
111
175
  this._toggleHovered = false;
112
176
  }
113
177
 
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
- }, WAIT_TIME);
124
- }
125
-
126
- private assignFilteredTrees() {
127
- this.filteredTrees = this._trees
128
- .map((singleTree) => {
129
- return {
130
- key: this.makeKey(),
131
- tree: this.searchText
132
- ? this.findMatch(
133
- new RegExp(this.searchText.toLowerCase().trim()),
134
- singleTree
135
- )!
136
- : singleTree
137
- };
138
- })
139
- .filter((filteredTree) => filteredTree.tree);
140
- }
141
-
142
178
  private makeKey(): string {
143
179
  return Math.random().toString(36).substring(7);
144
180
  }
145
181
 
146
- private findMatch(
147
- valuePattern: RegExp,
148
- node: TreeNode
149
- ): TreeNode | undefined {
150
- if (valuePattern.test(node.label.toLowerCase())) {
151
- return { ...node, isExpanded: true };
152
- }
182
+ private onSearchChange(e: CustomEvent) {
183
+ this.requestedFetchMoreResults = false;
184
+ this.searchResults = e.detail.results;
185
+ this.searchValue = e.detail.value;
186
+ }
153
187
 
154
- const filteredChildren = this.filterChildren(
155
- valuePattern,
156
- node.children
157
- );
158
- return filteredChildren.length > 0
159
- ? { ...node, children: filteredChildren, isExpanded: true }
160
- : undefined;
188
+ private onSearchLoading(e: CustomEvent) {
189
+ if (!e.detail.loading && this.scrollToSelectedSearchResult) {
190
+ this.selectedSearchResultIndex = this.searchResults.findIndex(
191
+ (r) => r.selected
192
+ );
193
+ }
194
+ this.isSearchLoading = e.detail;
161
195
  }
162
196
 
163
- private filterChildren(
164
- valuePattern: RegExp,
165
- children?: Array<TreeNode>
166
- ): Array<TreeNode> {
167
- return !children || children.length === 0
168
- ? []
169
- : (children
170
- .map((child) => this.findMatch(valuePattern, child))
171
- .filter((child) => child) as Array<TreeNode>);
197
+ private onSidebarSearchContentScroll() {
198
+ if (this.isSearchLoading) {
199
+ return;
200
+ }
201
+
202
+ const search = this.template.querySelector("dx-sidebar-search");
203
+
204
+ if (
205
+ this.isNearBottomOfSearchResults &&
206
+ search &&
207
+ !this.requestedFetchMoreResults
208
+ ) {
209
+ this.requestedFetchMoreResults = true;
210
+ search.fetchMoreResults();
211
+ }
172
212
  }
173
213
 
214
+ private initializeSearchScrollPosition = () => {
215
+ if (
216
+ this.scrollToSelectedSearchResult &&
217
+ this.selectedSearchResultIndex >= 0
218
+ ) {
219
+ const selectedResult = this.template.querySelector(
220
+ `dx-sidebar-search-result:nth-child(${
221
+ this.selectedSearchResultIndex + 1
222
+ })`
223
+ );
224
+ if (!selectedResult || !selectedResult.parentNode) {
225
+ return;
226
+ }
227
+ (selectedResult.parentNode as HTMLElement).scrollTop =
228
+ selectedResult.offsetTop -
229
+ (selectedResult.parentNode as HTMLElement).offsetTop;
230
+ this.scrollToSelectedSearchResult = false;
231
+ }
232
+ };
233
+
174
234
  private assignValueToLabel(node: TreeNode): void {
175
235
  this.valueToLabel[node.name] = node.label;
176
236
  if (node.children && node.children.length > 0) {