@salesforcedevs/dx-components 1.31.0 → 1.32.0-alpha.1

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.
@@ -2,34 +2,56 @@
2
2
  @import "dxHelpers/text";
3
3
  @import "dxHelpers/commonTreeItem";
4
4
 
5
+ /* List item states per spec: Default, Hover (#F7F7F7), Selected (#E6F2FF), Focus (blue outline), Focus+Selected */
5
6
  a {
6
7
  display: flex;
7
8
  flex-direction: column;
8
9
  }
9
10
 
11
+ /* Match "Results" heading alignment. Less item padding so total height unchanged when box has more inner padding. */
10
12
  .sidebar-item {
11
- padding: var(--dx-g-spacing-smd) var(--dx-g-spacing-lg)
12
- var(--dx-g-spacing-smd) var(--dx-g-global-header-padding-horizontal);
13
+ padding: var(--dx-g-spacing-xs) var(--dx-g-spacing-lg)
14
+ var(--dx-g-spacing-xs) var(--dx-c-sidebar-left-padding);
15
+ background: transparent;
16
+ transition: background-color var(--dx-g-transition-hue-1x, 0.1s ease);
17
+ overflow: visible;
13
18
  }
14
19
 
15
- .sidebar-item:not(.sidebar-item-selected):focus-visible {
16
- padding: var(--dx-g-spacing-smd) 0;
17
- margin: 0 var(--dx-g-spacing-lg) 0
18
- var(--dx-g-global-header-padding-horizontal);
19
- outline: var(--dx-g-spacing-2xs) solid var(--dx-g-blue-vibrant-40);
20
- border-radius: var(--dx-g-spacing-2xs);
20
+ /* Inner block: more vertical padding (12px) so box has more space top/bottom; 8px horizontal. margin-left so text aligns with Results. */
21
+ .search-result {
22
+ margin-left: calc(-1 * var(--dx-g-spacing-sm));
23
+ padding: var(--dx-g-spacing-smd) var(--dx-g-spacing-sm);
24
+ border: none;
25
+ border-radius: var(--dx-g-spacing-xs);
26
+ transition: background-color var(--dx-g-transition-hue-1x, 0.1s ease);
21
27
  }
22
28
 
23
- .sidebar-item-selected:focus-visible {
24
- padding: 0 var(--dx-g-spacing-lg) 0
25
- var(--dx-g-global-header-padding-horizontal);
26
- outline: none;
29
+ /* Hover: light grey background in box shape only */
30
+ .sidebar-item:not(.sidebar-item-selected):hover .search-result {
31
+ background: var(--dx-g-gray-95);
32
+ }
33
+
34
+ /* Selected: light blue background in box shape only */
35
+ .sidebar-item-selected {
36
+ box-shadow: none;
37
+ color: var(--dx-g-blue-vibrant-40) !important;
38
+ }
39
+
40
+ .sidebar-item-selected .search-result {
41
+ background: var(--dx-g-cloud-blue-vibrant-95);
42
+ color: inherit;
43
+ }
27
44
 
28
- .search-result {
29
- outline: var(--dx-g-spacing-2xs) solid var(--dx-g-blue-vibrant-40);
30
- border-radius: var(--dx-g-spacing-2xs);
31
- padding: var(--dx-g-spacing-smd) 0;
32
- }
45
+ /* Focus: only one box – remove anchor outline (from commonTreeItem), draw single blue outline on .search-result only */
46
+ .sidebar-item:focus-visible {
47
+ outline: none !important;
48
+ }
49
+
50
+ .sidebar-item:focus-visible .search-result {
51
+ outline: 2px solid var(--dx-g-blue-vibrant-50);
52
+ outline-offset: 0;
53
+ border-radius: var(--dx-g-spacing-xs);
54
+ border: none;
33
55
  }
34
56
 
35
57
  .search-text {
@@ -54,11 +76,9 @@ a {
54
76
  color: var(--dx-g-text-heading-color);
55
77
  }
56
78
 
57
- .title:hover {
58
- color: var(--dx-g-blue-vibrant-50);
59
- }
60
-
79
+ /* Match full-doc search highlight: light yellow from dx-css-variables */
61
80
  .bold {
81
+ background-color: var(--dx-g-yellow-vibrant-90);
62
82
  font-weight: 700;
63
83
  }
64
84
 
@@ -1,8 +1,8 @@
1
1
  import { LightningElement, api } from "lwc";
2
2
  import cx from "classnames";
3
- import { CoveoHighlights } from "typings/custom";
3
+ import { HighlightedSections } from "typings/custom";
4
4
 
5
- const toChunks = (value: string, highlights: CoveoHighlights) => {
5
+ const toChunks = (value: string, highlights: HighlightedSections) => {
6
6
  if (!highlights || highlights.length < 1) {
7
7
  return [
8
8
  {
@@ -51,11 +51,11 @@ const toChunks = (value: string, highlights: CoveoHighlights) => {
51
51
 
52
52
  export default class SidebarSearchResult extends LightningElement {
53
53
  @api description!: string;
54
- @api descriptionHighlights!: CoveoHighlights;
54
+ @api descriptionHighlights!: HighlightedSections;
55
55
  @api href!: string;
56
56
  @api selected!: boolean;
57
57
  @api header!: string;
58
- @api titleHighlights!: CoveoHighlights;
58
+ @api titleHighlights!: HighlightedSections;
59
59
  @api select!: Function;
60
60
 
61
61
  private get titleChunks() {
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Data 360 search API – shared endpoints and helpers for sidebar search,
3
+ * search results page, and has-results check.
4
+ */
5
+
6
+ const STORAGE_KEY_PREFIX = "data360-search:";
7
+
8
+ /** Data 360 Search API result item (title, url, matchedText) */
9
+ export interface Data360SearchResultItem {
10
+ title?: string;
11
+ url?: string;
12
+ matchedText?: string;
13
+ }
14
+
15
+ /** Normalized result shape stored in cache (no selected/select; those are applied on restore). */
16
+ export interface Data360SearchCacheItem {
17
+ title: string;
18
+ titleHighlights: Array<{ length: number; offset: number }>;
19
+ excerpt: string;
20
+ excerptHighlights: Array<{ length: number; offset: number }>;
21
+ uniqueId: string;
22
+ href: string;
23
+ }
24
+
25
+ export interface Data360SearchCache {
26
+ query: string;
27
+ results: Data360SearchCacheItem[];
28
+ /** Timestamp when stored (Date.now()); missing on older entries (treated as stale). */
29
+ storedAt?: number;
30
+ }
31
+
32
+ const CACHE_MAX_AGE_MS = 2 * 60 * 60 * 1000; // 2 hours
33
+
34
+ export function isCacheStale(cache: Data360SearchCache | null): boolean {
35
+ if (!cache?.storedAt) {
36
+ return true;
37
+ }
38
+ return Date.now() - cache.storedAt > CACHE_MAX_AGE_MS;
39
+ }
40
+
41
+ export function getStoredSearch(
42
+ baseUrlPath: string
43
+ ): Data360SearchCache | null {
44
+ try {
45
+ const raw = localStorage.getItem(`${STORAGE_KEY_PREFIX}${baseUrlPath}`);
46
+ if (!raw) {
47
+ return null;
48
+ }
49
+ const parsed = JSON.parse(raw) as Data360SearchCache;
50
+ if (
51
+ !parsed ||
52
+ typeof parsed.query !== "string" ||
53
+ !Array.isArray(parsed.results)
54
+ ) {
55
+ return null;
56
+ }
57
+ return parsed;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ export function setStoredSearch(
64
+ baseUrlPath: string,
65
+ data: Data360SearchCache
66
+ ): void {
67
+ try {
68
+ const toStore: Data360SearchCache = {
69
+ ...data,
70
+ storedAt: Date.now()
71
+ };
72
+ localStorage.setItem(
73
+ `${STORAGE_KEY_PREFIX}${baseUrlPath}`,
74
+ JSON.stringify(toStore)
75
+ );
76
+ } catch {
77
+ /* ignore quota or disabled localStorage */
78
+ }
79
+ }
80
+
81
+ export const DATA_360_SEARCH_PATH = "/data-360-search/search";
82
+ export const DATA_360_HAS_RESULTS_PATH = "/data-360-search/has-results";
83
+ export const DATA_360_SEARCH_ORIGIN = "https://developer.salesforce.com";
84
+
85
+ export function getBaseUrlPath(): string {
86
+ const url = DATA_360_SEARCH_ORIGIN + window.location.pathname;
87
+ const lastSlash = url.lastIndexOf("/");
88
+ return lastSlash > 0 ? url.substring(0, lastSlash) : url;
89
+ }
90
+
91
+ /**
92
+ * Read search query from URL: ?q=, ?keywords= (migrated to q), or #q=.
93
+ */
94
+ export function getQueryFromUrl(): string {
95
+ const params = new URLSearchParams(window.location.search);
96
+ const keywords = params.get("keywords");
97
+ if (keywords) {
98
+ const url = new URL(window.location.href);
99
+ url.searchParams.delete("keywords");
100
+ url.searchParams.set("q", keywords);
101
+ window.history.replaceState(null, "", url.href);
102
+ return keywords;
103
+ }
104
+ const qFromSearch = params.get("q");
105
+ if (qFromSearch) {
106
+ return qFromSearch;
107
+ }
108
+ const hash = window.location.hash.slice(1);
109
+ if (hash) {
110
+ try {
111
+ const qFromHash = new URLSearchParams(hash).get("q");
112
+ if (qFromHash) {
113
+ return qFromHash;
114
+ }
115
+ } catch {
116
+ /* ignore */
117
+ }
118
+ }
119
+ return "";
120
+ }
121
+
122
+ /**
123
+ * Quick check if the current page has searchable results.
124
+ * Only when this returns true should the Data 360 sidebar be shown.
125
+ */
126
+ export async function fetchHasResults(): Promise<boolean> {
127
+ try {
128
+ const res = await fetch(DATA_360_HAS_RESULTS_PATH, {
129
+ method: "POST",
130
+ headers: { "Content-Type": "application/json" },
131
+ body: JSON.stringify({ baseUrlPath: getBaseUrlPath() })
132
+ });
133
+ if (!res.ok) {
134
+ return false;
135
+ }
136
+ const data = await res.json();
137
+ return data.hasResults === true;
138
+ } catch {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Run a Data 360 search. Returns the raw result items; callers normalize for UI.
145
+ */
146
+ export async function fetchSearch(
147
+ searchQuery: string
148
+ ): Promise<Data360SearchResultItem[]> {
149
+ const query = searchQuery.trim();
150
+ if (!query) {
151
+ return [];
152
+ }
153
+ const res = await fetch(DATA_360_SEARCH_PATH, {
154
+ method: "POST",
155
+ headers: { "Content-Type": "application/json" },
156
+ body: JSON.stringify({
157
+ searchQuery: query,
158
+ baseUrlPath: getBaseUrlPath()
159
+ })
160
+ });
161
+ if (!res.ok) {
162
+ throw new Error(`Search API error: ${res.status}`);
163
+ }
164
+ const data = await res.json();
165
+ return Array.isArray(data)
166
+ ? data
167
+ : data?.results ?? data?.data?.results ?? [];
168
+ }