@seekora-ai/ui-sdk-react 1.0.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 (169) hide show
  1. package/dist/components/Breadcrumb.d.ts +43 -0
  2. package/dist/components/Breadcrumb.d.ts.map +1 -0
  3. package/dist/components/Breadcrumb.js +119 -0
  4. package/dist/components/ClearRefinements.d.ts +42 -0
  5. package/dist/components/ClearRefinements.d.ts.map +1 -0
  6. package/dist/components/ClearRefinements.js +80 -0
  7. package/dist/components/CurrentRefinements.d.ts +41 -0
  8. package/dist/components/CurrentRefinements.d.ts.map +1 -0
  9. package/dist/components/CurrentRefinements.js +83 -0
  10. package/dist/components/Facets.d.ts +53 -0
  11. package/dist/components/Facets.d.ts.map +1 -0
  12. package/dist/components/Facets.js +195 -0
  13. package/dist/components/FederatedDropdown.d.ts +92 -0
  14. package/dist/components/FederatedDropdown.d.ts.map +1 -0
  15. package/dist/components/FederatedDropdown.js +510 -0
  16. package/dist/components/HierarchicalMenu.d.ts +55 -0
  17. package/dist/components/HierarchicalMenu.d.ts.map +1 -0
  18. package/dist/components/HierarchicalMenu.js +168 -0
  19. package/dist/components/Highlight.d.ts +51 -0
  20. package/dist/components/Highlight.d.ts.map +1 -0
  21. package/dist/components/Highlight.js +155 -0
  22. package/dist/components/HitsPerPage.d.ts +41 -0
  23. package/dist/components/HitsPerPage.d.ts.map +1 -0
  24. package/dist/components/HitsPerPage.js +72 -0
  25. package/dist/components/InfiniteHits.d.ts +56 -0
  26. package/dist/components/InfiniteHits.d.ts.map +1 -0
  27. package/dist/components/InfiniteHits.js +181 -0
  28. package/dist/components/MobileFilters.d.ts +71 -0
  29. package/dist/components/MobileFilters.d.ts.map +1 -0
  30. package/dist/components/MobileFilters.js +242 -0
  31. package/dist/components/Pagination.d.ts +44 -0
  32. package/dist/components/Pagination.d.ts.map +1 -0
  33. package/dist/components/Pagination.js +142 -0
  34. package/dist/components/QuerySuggestions.d.ts +38 -0
  35. package/dist/components/QuerySuggestions.d.ts.map +1 -0
  36. package/dist/components/QuerySuggestions.js +86 -0
  37. package/dist/components/QuerySuggestionsDropdown.d.ts +86 -0
  38. package/dist/components/QuerySuggestionsDropdown.d.ts.map +1 -0
  39. package/dist/components/QuerySuggestionsDropdown.js +395 -0
  40. package/dist/components/RangeInput.d.ts +58 -0
  41. package/dist/components/RangeInput.d.ts.map +1 -0
  42. package/dist/components/RangeInput.js +203 -0
  43. package/dist/components/RangeSlider.d.ts +51 -0
  44. package/dist/components/RangeSlider.d.ts.map +1 -0
  45. package/dist/components/RangeSlider.js +193 -0
  46. package/dist/components/Recommendations.d.ts +90 -0
  47. package/dist/components/Recommendations.d.ts.map +1 -0
  48. package/dist/components/Recommendations.js +270 -0
  49. package/dist/components/RichQuerySuggestions.d.ts +77 -0
  50. package/dist/components/RichQuerySuggestions.d.ts.map +1 -0
  51. package/dist/components/RichQuerySuggestions.js +492 -0
  52. package/dist/components/SearchBar.d.ts +40 -0
  53. package/dist/components/SearchBar.d.ts.map +1 -0
  54. package/dist/components/SearchBar.js +217 -0
  55. package/dist/components/SearchBarWithSuggestions.d.ts +99 -0
  56. package/dist/components/SearchBarWithSuggestions.d.ts.map +1 -0
  57. package/dist/components/SearchBarWithSuggestions.js +275 -0
  58. package/dist/components/SearchLayout.d.ts +35 -0
  59. package/dist/components/SearchLayout.d.ts.map +1 -0
  60. package/dist/components/SearchLayout.js +56 -0
  61. package/dist/components/SearchProvider.d.ts +28 -0
  62. package/dist/components/SearchProvider.d.ts.map +1 -0
  63. package/dist/components/SearchProvider.js +43 -0
  64. package/dist/components/SearchResults.d.ts +51 -0
  65. package/dist/components/SearchResults.d.ts.map +1 -0
  66. package/dist/components/SearchResults.js +485 -0
  67. package/dist/components/SortBy.d.ts +44 -0
  68. package/dist/components/SortBy.d.ts.map +1 -0
  69. package/dist/components/SortBy.js +61 -0
  70. package/dist/components/Stats.d.ts +37 -0
  71. package/dist/components/Stats.d.ts.map +1 -0
  72. package/dist/components/Stats.js +52 -0
  73. package/dist/components/suggestions/AmazonDropdown.d.ts +30 -0
  74. package/dist/components/suggestions/AmazonDropdown.d.ts.map +1 -0
  75. package/dist/components/suggestions/AmazonDropdown.js +529 -0
  76. package/dist/components/suggestions/GoogleDropdown.d.ts +31 -0
  77. package/dist/components/suggestions/GoogleDropdown.d.ts.map +1 -0
  78. package/dist/components/suggestions/GoogleDropdown.js +370 -0
  79. package/dist/components/suggestions/MinimalDropdown.d.ts +24 -0
  80. package/dist/components/suggestions/MinimalDropdown.d.ts.map +1 -0
  81. package/dist/components/suggestions/MinimalDropdown.js +314 -0
  82. package/dist/components/suggestions/MobileSheetDropdown.d.ts +31 -0
  83. package/dist/components/suggestions/MobileSheetDropdown.d.ts.map +1 -0
  84. package/dist/components/suggestions/MobileSheetDropdown.js +485 -0
  85. package/dist/components/suggestions/PinterestDropdown.d.ts +29 -0
  86. package/dist/components/suggestions/PinterestDropdown.d.ts.map +1 -0
  87. package/dist/components/suggestions/PinterestDropdown.js +450 -0
  88. package/dist/components/suggestions/ShopifyDropdown.d.ts +27 -0
  89. package/dist/components/suggestions/ShopifyDropdown.d.ts.map +1 -0
  90. package/dist/components/suggestions/ShopifyDropdown.js +451 -0
  91. package/dist/components/suggestions/SpotlightDropdown.d.ts +33 -0
  92. package/dist/components/suggestions/SpotlightDropdown.d.ts.map +1 -0
  93. package/dist/components/suggestions/SpotlightDropdown.js +547 -0
  94. package/dist/components/suggestions/SuggestionSearchBar.d.ts +123 -0
  95. package/dist/components/suggestions/SuggestionSearchBar.d.ts.map +1 -0
  96. package/dist/components/suggestions/SuggestionSearchBar.js +652 -0
  97. package/dist/components/suggestions/index.d.ts +37 -0
  98. package/dist/components/suggestions/index.d.ts.map +1 -0
  99. package/dist/components/suggestions/index.js +59 -0
  100. package/dist/components/suggestions/styles/index.d.ts +11 -0
  101. package/dist/components/suggestions/styles/index.d.ts.map +1 -0
  102. package/dist/components/suggestions/styles/index.js +289 -0
  103. package/dist/components/suggestions/styles/responsive.d.ts +107 -0
  104. package/dist/components/suggestions/styles/responsive.d.ts.map +1 -0
  105. package/dist/components/suggestions/styles/responsive.js +237 -0
  106. package/dist/components/suggestions/types.d.ts +489 -0
  107. package/dist/components/suggestions/types.d.ts.map +1 -0
  108. package/dist/components/suggestions/types.js +6 -0
  109. package/dist/components/suggestions/utils.d.ts +213 -0
  110. package/dist/components/suggestions/utils.d.ts.map +1 -0
  111. package/dist/components/suggestions/utils.js +514 -0
  112. package/dist/hooks/useAnalytics.d.ts +20 -0
  113. package/dist/hooks/useAnalytics.d.ts.map +1 -0
  114. package/dist/hooks/useAnalytics.js +62 -0
  115. package/dist/hooks/useNaturalLanguageFilters.d.ts +48 -0
  116. package/dist/hooks/useNaturalLanguageFilters.d.ts.map +1 -0
  117. package/dist/hooks/useNaturalLanguageFilters.js +221 -0
  118. package/dist/hooks/useQuerySuggestions.d.ts +21 -0
  119. package/dist/hooks/useQuerySuggestions.d.ts.map +1 -0
  120. package/dist/hooks/useQuerySuggestions.js +68 -0
  121. package/dist/hooks/useQuerySuggestionsEnhanced.d.ts +114 -0
  122. package/dist/hooks/useQuerySuggestionsEnhanced.d.ts.map +1 -0
  123. package/dist/hooks/useQuerySuggestionsEnhanced.js +376 -0
  124. package/dist/hooks/useSearchState.d.ts +35 -0
  125. package/dist/hooks/useSearchState.d.ts.map +1 -0
  126. package/dist/hooks/useSearchState.js +68 -0
  127. package/dist/hooks/useSeekoraSearch.d.ts +20 -0
  128. package/dist/hooks/useSeekoraSearch.d.ts.map +1 -0
  129. package/dist/hooks/useSeekoraSearch.js +63 -0
  130. package/dist/hooks/useSmartSuggestions.d.ts +55 -0
  131. package/dist/hooks/useSmartSuggestions.d.ts.map +1 -0
  132. package/dist/hooks/useSmartSuggestions.js +236 -0
  133. package/dist/hooks/useSuggestionsAnalytics.d.ts +91 -0
  134. package/dist/hooks/useSuggestionsAnalytics.d.ts.map +1 -0
  135. package/dist/hooks/useSuggestionsAnalytics.js +226 -0
  136. package/dist/index.d.ts +80 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +86 -0
  139. package/dist/index.umd.js +1 -0
  140. package/dist/src/index.d.ts +2849 -0
  141. package/dist/src/index.esm.js +11679 -0
  142. package/dist/src/index.esm.js.map +1 -0
  143. package/dist/src/index.js +11761 -0
  144. package/dist/src/index.js.map +1 -0
  145. package/dist/themes/createTheme.d.ts +8 -0
  146. package/dist/themes/createTheme.d.ts.map +1 -0
  147. package/dist/themes/createTheme.js +10 -0
  148. package/dist/themes/dark.d.ts +6 -0
  149. package/dist/themes/dark.d.ts.map +1 -0
  150. package/dist/themes/dark.js +34 -0
  151. package/dist/themes/default.d.ts +6 -0
  152. package/dist/themes/default.d.ts.map +1 -0
  153. package/dist/themes/default.js +71 -0
  154. package/dist/themes/mergeThemes.d.ts +7 -0
  155. package/dist/themes/mergeThemes.d.ts.map +1 -0
  156. package/dist/themes/mergeThemes.js +6 -0
  157. package/dist/themes/minimal.d.ts +6 -0
  158. package/dist/themes/minimal.d.ts.map +1 -0
  159. package/dist/themes/minimal.js +34 -0
  160. package/dist/themes/suggestions.d.ts +216 -0
  161. package/dist/themes/suggestions.d.ts.map +1 -0
  162. package/dist/themes/suggestions.js +546 -0
  163. package/dist/themes/types.d.ts +7 -0
  164. package/dist/themes/types.d.ts.map +1 -0
  165. package/dist/themes/types.js +6 -0
  166. package/dist/types/index.d.ts +33 -0
  167. package/dist/types/index.d.ts.map +1 -0
  168. package/dist/types/index.js +4 -0
  169. package/package.json +65 -0
@@ -0,0 +1,514 @@
1
+ /**
2
+ * Utility functions for Query Suggestions components
3
+ */
4
+ // ============================================================================
5
+ // Field Extraction
6
+ // ============================================================================
7
+ /**
8
+ * Get nested value from object using dot notation
9
+ * @example getNestedValue({ a: { b: 'value' } }, 'a.b') => 'value'
10
+ */
11
+ export const getNestedValue = (obj, path) => {
12
+ if (!obj || !path)
13
+ return undefined;
14
+ return path.split('.').reduce((current, key) => {
15
+ if (current === null || current === undefined)
16
+ return undefined;
17
+ return current[key];
18
+ }, obj);
19
+ };
20
+ /**
21
+ * Extract suggestion fields from raw data
22
+ */
23
+ export const extractSuggestion = (item, mapping = { query: 'query' }) => {
24
+ return {
25
+ query: getNestedValue(item, mapping.query) ?? '',
26
+ count: mapping.count ? getNestedValue(item, mapping.count) : undefined,
27
+ id: mapping.id ? getNestedValue(item, mapping.id) : item?.objectID || item?.id,
28
+ categories: mapping.categories ? getNestedValue(item, mapping.categories) : undefined,
29
+ highlighted: mapping.highlighted ? getNestedValue(item, mapping.highlighted) : undefined,
30
+ _raw: item,
31
+ };
32
+ };
33
+ /**
34
+ * Extract product fields from raw data
35
+ */
36
+ export const extractProduct = (item, mapping = { id: 'id', title: 'title' }) => {
37
+ return {
38
+ id: getNestedValue(item, mapping.id) ?? item?.objectID ?? item?.id,
39
+ title: getNestedValue(item, mapping.title) ?? '',
40
+ image: mapping.image ? getNestedValue(item, mapping.image) : undefined,
41
+ price: mapping.price ? getNestedValue(item, mapping.price) : undefined,
42
+ comparePrice: mapping.comparePrice ? getNestedValue(item, mapping.comparePrice) : undefined,
43
+ url: mapping.url ? getNestedValue(item, mapping.url) : undefined,
44
+ brand: mapping.brand ? getNestedValue(item, mapping.brand) : undefined,
45
+ category: mapping.category ? getNestedValue(item, mapping.category) : undefined,
46
+ rating: mapping.rating ? getNestedValue(item, mapping.rating) : undefined,
47
+ reviewCount: mapping.reviewCount ? getNestedValue(item, mapping.reviewCount) : undefined,
48
+ discount: mapping.discount ? getNestedValue(item, mapping.discount) : undefined,
49
+ inStock: mapping.inStock ? getNestedValue(item, mapping.inStock) : undefined,
50
+ currency: mapping.currency ? getNestedValue(item, mapping.currency) : undefined,
51
+ _raw: item,
52
+ };
53
+ };
54
+ /**
55
+ * Extract category fields from raw data
56
+ */
57
+ export const extractCategory = (item, mapping = { id: 'id', label: 'label' }) => {
58
+ return {
59
+ id: getNestedValue(item, mapping.id) ?? item?.id,
60
+ label: getNestedValue(item, mapping.label) ?? '',
61
+ count: mapping.count ? getNestedValue(item, mapping.count) : undefined,
62
+ icon: mapping.icon ? getNestedValue(item, mapping.icon) : undefined,
63
+ image: mapping.image ? getNestedValue(item, mapping.image) : undefined,
64
+ _raw: item,
65
+ };
66
+ };
67
+ /**
68
+ * Extract brand fields from raw data
69
+ */
70
+ export const extractBrand = (item, mapping = { name: 'name' }) => {
71
+ return {
72
+ id: mapping.id ? getNestedValue(item, mapping.id) : item?.id,
73
+ name: getNestedValue(item, mapping.name) ?? '',
74
+ logo: mapping.logo ? getNestedValue(item, mapping.logo) : undefined,
75
+ count: mapping.count ? getNestedValue(item, mapping.count) : undefined,
76
+ _raw: item,
77
+ };
78
+ };
79
+ // ============================================================================
80
+ // Formatting
81
+ // ============================================================================
82
+ /**
83
+ * Format price with currency
84
+ */
85
+ export const formatPrice = (value, config = {}) => {
86
+ if (value === undefined || value === null)
87
+ return '';
88
+ const { currency = '$', currencyPosition = 'before', priceDecimals = 2 } = config;
89
+ const num = typeof value === 'string' ? parseFloat(value) : value;
90
+ if (isNaN(num))
91
+ return String(value);
92
+ const formatted = num.toLocaleString(undefined, {
93
+ minimumFractionDigits: priceDecimals,
94
+ maximumFractionDigits: priceDecimals,
95
+ });
96
+ return currencyPosition === 'before'
97
+ ? `${currency}${formatted}`
98
+ : `${formatted}${currency}`;
99
+ };
100
+ /**
101
+ * Format count with abbreviation (1.2K, 3.4M, etc.)
102
+ */
103
+ export const formatCount = (count) => {
104
+ if (count === undefined || count === null)
105
+ return '';
106
+ if (count < 1000)
107
+ return count.toString();
108
+ if (count < 1000000)
109
+ return `${(count / 1000).toFixed(1)}K`;
110
+ return `${(count / 1000000).toFixed(1)}M`;
111
+ };
112
+ /**
113
+ * Calculate discount percentage
114
+ */
115
+ export const calculateDiscount = (price, comparePrice) => {
116
+ if (!price || !comparePrice || comparePrice <= price)
117
+ return undefined;
118
+ return Math.round(((comparePrice - price) / comparePrice) * 100);
119
+ };
120
+ // ============================================================================
121
+ // Text Processing
122
+ // ============================================================================
123
+ /**
124
+ * Escape HTML special characters
125
+ */
126
+ export const escapeHtml = (text) => {
127
+ const map = {
128
+ '&': '&amp;',
129
+ '<': '&lt;',
130
+ '>': '&gt;',
131
+ '"': '&quot;',
132
+ "'": '&#39;',
133
+ };
134
+ return text.replace(/[&<>"']/g, (char) => map[char]);
135
+ };
136
+ /**
137
+ * Highlight matching text in a string
138
+ */
139
+ export const highlightText = (text, query, options = {}) => {
140
+ if (!query || !text)
141
+ return escapeHtml(text);
142
+ const { tag = 'mark', className = '', style } = options;
143
+ const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
144
+ const regex = new RegExp(`(${escapedQuery})`, 'gi');
145
+ const styleAttr = style
146
+ ? ` style="${Object.entries(style).map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}:${v}`).join(';')}"`
147
+ : '';
148
+ const classAttr = className ? ` class="${className}"` : '';
149
+ return escapeHtml(text).replace(new RegExp(`(${escapeHtml(escapedQuery)})`, 'gi'), `<${tag}${classAttr}${styleAttr}>$1</${tag}>`);
150
+ };
151
+ /**
152
+ * Truncate text to specified length
153
+ */
154
+ export const truncateText = (text, maxLength, ellipsis = '...') => {
155
+ if (!text || text.length <= maxLength)
156
+ return text;
157
+ return text.slice(0, maxLength - ellipsis.length) + ellipsis;
158
+ };
159
+ // ============================================================================
160
+ // Theme & Styling
161
+ // ============================================================================
162
+ /**
163
+ * Generate CSS variables from theme config
164
+ */
165
+ export const generateCSSVariables = (theme, prefix = 'seekora') => {
166
+ const vars = {};
167
+ if (theme.primaryColor)
168
+ vars[`--${prefix}-primary`] = theme.primaryColor;
169
+ if (theme.backgroundColor)
170
+ vars[`--${prefix}-bg-surface`] = theme.backgroundColor;
171
+ if (theme.surfaceColor)
172
+ vars[`--${prefix}-bg-secondary`] = theme.surfaceColor;
173
+ if (theme.textColor)
174
+ vars[`--${prefix}-text-primary`] = theme.textColor;
175
+ if (theme.textSecondaryColor)
176
+ vars[`--${prefix}-text-secondary`] = theme.textSecondaryColor;
177
+ if (theme.borderColor)
178
+ vars[`--${prefix}-border-color`] = theme.borderColor;
179
+ if (theme.hoverColor)
180
+ vars[`--${prefix}-bg-hover`] = theme.hoverColor;
181
+ if (theme.highlightColor)
182
+ vars[`--${prefix}-highlight-bg`] = theme.highlightColor;
183
+ if (theme.successColor)
184
+ vars[`--${prefix}-success`] = theme.successColor;
185
+ if (theme.errorColor)
186
+ vars[`--${prefix}-error`] = theme.errorColor;
187
+ if (theme.fontFamily)
188
+ vars[`--${prefix}-font-family`] = theme.fontFamily;
189
+ if (theme.fontSize)
190
+ vars[`--${prefix}-font-size`] = typeof theme.fontSize === 'number' ? `${theme.fontSize}px` : theme.fontSize;
191
+ if (theme.borderRadius)
192
+ vars[`--${prefix}-border-radius`] = typeof theme.borderRadius === 'number' ? `${theme.borderRadius}px` : theme.borderRadius;
193
+ if (theme.boxShadow)
194
+ vars[`--${prefix}-box-shadow`] = theme.boxShadow;
195
+ // Merge custom CSS variables
196
+ if (theme.cssVariables) {
197
+ Object.entries(theme.cssVariables).forEach(([key, value]) => {
198
+ vars[key.startsWith('--') ? key : `--${key}`] = value;
199
+ });
200
+ }
201
+ return vars;
202
+ };
203
+ /**
204
+ * Merge class names, filtering out falsy values
205
+ */
206
+ export const cx = (...classes) => {
207
+ return classes.filter(Boolean).join(' ');
208
+ };
209
+ /**
210
+ * Merge styles, filtering out undefined values
211
+ */
212
+ export const mergeStyles = (...styles) => {
213
+ return Object.assign({}, ...styles.filter(Boolean));
214
+ };
215
+ export const animations = {
216
+ fade: {
217
+ initial: { opacity: 0 },
218
+ animate: { opacity: 1 },
219
+ exit: { opacity: 0 },
220
+ },
221
+ slide: {
222
+ initial: { opacity: 0, transform: 'translateY(-10px)' },
223
+ animate: { opacity: 1, transform: 'translateY(0)' },
224
+ exit: { opacity: 0, transform: 'translateY(-10px)' },
225
+ },
226
+ scale: {
227
+ initial: { opacity: 0, transform: 'scale(0.95)' },
228
+ animate: { opacity: 1, transform: 'scale(1)' },
229
+ exit: { opacity: 0, transform: 'scale(0.95)' },
230
+ },
231
+ spring: {
232
+ initial: { opacity: 0, transform: 'translateY(-20px) scale(0.9)' },
233
+ animate: { opacity: 1, transform: 'translateY(0) scale(1)' },
234
+ exit: { opacity: 0, transform: 'translateY(-10px) scale(0.95)' },
235
+ },
236
+ };
237
+ /**
238
+ * Get animation keyframes CSS
239
+ */
240
+ export const getAnimationCSS = (type, duration = 200) => {
241
+ if (type === 'none')
242
+ return '';
243
+ const anim = animations[type];
244
+ return `
245
+ @keyframes seekoraDropdownEnter {
246
+ from {
247
+ opacity: ${anim.initial.opacity};
248
+ transform: ${anim.initial.transform || 'none'};
249
+ }
250
+ to {
251
+ opacity: ${anim.animate.opacity};
252
+ transform: ${anim.animate.transform || 'none'};
253
+ }
254
+ }
255
+ @keyframes seekoraDropdownExit {
256
+ from {
257
+ opacity: ${anim.animate.opacity};
258
+ transform: ${anim.animate.transform || 'none'};
259
+ }
260
+ to {
261
+ opacity: ${anim.exit.opacity};
262
+ transform: ${anim.exit.transform || 'none'};
263
+ }
264
+ }
265
+ `;
266
+ };
267
+ // ============================================================================
268
+ // Keyboard Navigation
269
+ // ============================================================================
270
+ /**
271
+ * Create keyboard handler for dropdown navigation
272
+ */
273
+ export const createKeyboardHandler = (handlers) => {
274
+ return (event) => {
275
+ switch (event.key) {
276
+ case 'ArrowUp':
277
+ event.preventDefault();
278
+ handlers.onUp?.();
279
+ break;
280
+ case 'ArrowDown':
281
+ event.preventDefault();
282
+ handlers.onDown?.();
283
+ break;
284
+ case 'ArrowLeft':
285
+ handlers.onLeft?.();
286
+ break;
287
+ case 'ArrowRight':
288
+ handlers.onRight?.();
289
+ break;
290
+ case 'Enter':
291
+ event.preventDefault();
292
+ handlers.onEnter?.();
293
+ break;
294
+ case 'Escape':
295
+ event.preventDefault();
296
+ handlers.onEscape?.();
297
+ break;
298
+ case 'Tab':
299
+ handlers.onTab?.(event.shiftKey);
300
+ break;
301
+ }
302
+ };
303
+ };
304
+ // ============================================================================
305
+ // Local Storage (Recent Searches)
306
+ // ============================================================================
307
+ const RECENT_SEARCHES_KEY = 'seekora_recent_searches';
308
+ const MAX_RECENT_SEARCHES = 10;
309
+ export const getRecentSearches = (storeId) => {
310
+ try {
311
+ const key = storeId ? `${RECENT_SEARCHES_KEY}_${storeId}` : RECENT_SEARCHES_KEY;
312
+ const stored = localStorage.getItem(key);
313
+ return stored ? JSON.parse(stored) : [];
314
+ }
315
+ catch {
316
+ return [];
317
+ }
318
+ };
319
+ export const addRecentSearch = (query, storeId) => {
320
+ try {
321
+ const key = storeId ? `${RECENT_SEARCHES_KEY}_${storeId}` : RECENT_SEARCHES_KEY;
322
+ let searches = getRecentSearches(storeId);
323
+ searches = searches.filter(s => s !== query);
324
+ searches.unshift(query);
325
+ searches = searches.slice(0, MAX_RECENT_SEARCHES);
326
+ localStorage.setItem(key, JSON.stringify(searches));
327
+ return searches;
328
+ }
329
+ catch {
330
+ return [];
331
+ }
332
+ };
333
+ export const removeRecentSearch = (query, storeId) => {
334
+ try {
335
+ const key = storeId ? `${RECENT_SEARCHES_KEY}_${storeId}` : RECENT_SEARCHES_KEY;
336
+ let searches = getRecentSearches(storeId);
337
+ searches = searches.filter(s => s !== query);
338
+ localStorage.setItem(key, JSON.stringify(searches));
339
+ return searches;
340
+ }
341
+ catch {
342
+ return [];
343
+ }
344
+ };
345
+ export const clearRecentSearches = (storeId) => {
346
+ try {
347
+ const key = storeId ? `${RECENT_SEARCHES_KEY}_${storeId}` : RECENT_SEARCHES_KEY;
348
+ localStorage.removeItem(key);
349
+ }
350
+ catch {
351
+ // Silent fail
352
+ }
353
+ };
354
+ // ============================================================================
355
+ // Misc Utilities
356
+ // ============================================================================
357
+ /**
358
+ * Debounce function
359
+ */
360
+ export const debounce = (fn, delay) => {
361
+ let timeoutId = null;
362
+ const debounced = (...args) => {
363
+ if (timeoutId)
364
+ clearTimeout(timeoutId);
365
+ timeoutId = setTimeout(() => fn(...args), delay);
366
+ };
367
+ debounced.cancel = () => {
368
+ if (timeoutId)
369
+ clearTimeout(timeoutId);
370
+ };
371
+ return debounced;
372
+ };
373
+ /**
374
+ * Generate unique ID
375
+ */
376
+ export const generateId = (prefix = 'seekora') => {
377
+ return `${prefix}-${Math.random().toString(36).substring(2, 9)}`;
378
+ };
379
+ /**
380
+ * Check if element is in viewport
381
+ */
382
+ export const isInViewport = (element) => {
383
+ const rect = element.getBoundingClientRect();
384
+ return (rect.top >= 0 &&
385
+ rect.left >= 0 &&
386
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
387
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth));
388
+ };
389
+ /**
390
+ * Scroll element into view if needed
391
+ */
392
+ export const scrollIntoViewIfNeeded = (element, container) => {
393
+ const elementRect = element.getBoundingClientRect();
394
+ const containerRect = container.getBoundingClientRect();
395
+ if (elementRect.top < containerRect.top) {
396
+ element.scrollIntoView({ block: 'start', behavior: 'smooth' });
397
+ }
398
+ else if (elementRect.bottom > containerRect.bottom) {
399
+ element.scrollIntoView({ block: 'end', behavior: 'smooth' });
400
+ }
401
+ };
402
+ /**
403
+ * Simple in-memory cache with TTL support for suggestions
404
+ * Reduces API calls by caching results for quick repeated queries
405
+ */
406
+ class SuggestionsCache {
407
+ constructor(options = {}) {
408
+ this.cache = new Map();
409
+ this.maxSize = options.maxSize ?? 100;
410
+ this.defaultTtl = options.defaultTtlMs ?? 30000; // 30 seconds default
411
+ }
412
+ /**
413
+ * Generate a cache key from query and options
414
+ */
415
+ generateKey(query, options) {
416
+ const normalizedQuery = query.toLowerCase().trim();
417
+ if (!options)
418
+ return normalizedQuery;
419
+ // Create a stable key from options
420
+ const optionsKey = Object.keys(options)
421
+ .sort()
422
+ .filter(k => options[k] !== undefined && options[k] !== null)
423
+ .map(k => `${k}:${JSON.stringify(options[k])}`)
424
+ .join('|');
425
+ return optionsKey ? `${normalizedQuery}::${optionsKey}` : normalizedQuery;
426
+ }
427
+ /**
428
+ * Get cached data if valid (not expired)
429
+ */
430
+ get(key) {
431
+ const entry = this.cache.get(key);
432
+ if (!entry)
433
+ return null;
434
+ const now = Date.now();
435
+ if (now - entry.timestamp > entry.ttl) {
436
+ // Expired - remove and return null
437
+ this.cache.delete(key);
438
+ return null;
439
+ }
440
+ return entry.data;
441
+ }
442
+ /**
443
+ * Store data in cache
444
+ */
445
+ set(key, data, ttlMs) {
446
+ // Evict oldest entries if at max size
447
+ if (this.cache.size >= this.maxSize) {
448
+ const oldestKey = this.cache.keys().next().value;
449
+ if (oldestKey)
450
+ this.cache.delete(oldestKey);
451
+ }
452
+ this.cache.set(key, {
453
+ data,
454
+ timestamp: Date.now(),
455
+ ttl: ttlMs ?? this.defaultTtl,
456
+ });
457
+ }
458
+ /**
459
+ * Check if key exists and is valid
460
+ */
461
+ has(key) {
462
+ return this.get(key) !== null;
463
+ }
464
+ /**
465
+ * Clear all cached entries
466
+ */
467
+ clear() {
468
+ this.cache.clear();
469
+ }
470
+ /**
471
+ * Clear expired entries
472
+ */
473
+ cleanup() {
474
+ const now = Date.now();
475
+ for (const [key, entry] of this.cache.entries()) {
476
+ if (now - entry.timestamp > entry.ttl) {
477
+ this.cache.delete(key);
478
+ }
479
+ }
480
+ }
481
+ /**
482
+ * Get cache statistics
483
+ */
484
+ getStats() {
485
+ return {
486
+ size: this.cache.size,
487
+ maxSize: this.maxSize,
488
+ };
489
+ }
490
+ }
491
+ // Global cache instance for suggestions (shared across components)
492
+ let globalSuggestionsCache = null;
493
+ /**
494
+ * Get the global suggestions cache instance
495
+ */
496
+ export const getSuggestionsCache = (options) => {
497
+ if (!globalSuggestionsCache) {
498
+ globalSuggestionsCache = new SuggestionsCache(options);
499
+ }
500
+ return globalSuggestionsCache;
501
+ };
502
+ /**
503
+ * Create a new cache instance (for isolated caching per component)
504
+ */
505
+ export const createSuggestionsCache = (options) => {
506
+ return new SuggestionsCache(options);
507
+ };
508
+ /**
509
+ * Clear the global cache
510
+ */
511
+ export const clearSuggestionsCache = () => {
512
+ globalSuggestionsCache?.clear();
513
+ };
514
+ export { SuggestionsCache };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * useAnalytics Hook
3
+ *
4
+ * Hook for tracking analytics events with the Seekora SDK
5
+ */
6
+ import type { SeekoraClient, SearchContext, DataTypesEventPayload } from '@seekora-ai/search-sdk';
7
+ export interface UseAnalyticsOptions {
8
+ client: SeekoraClient;
9
+ enabled?: boolean;
10
+ }
11
+ export interface UseAnalyticsReturn {
12
+ trackEvent: (eventType: string, payload: Partial<DataTypesEventPayload>, context?: SearchContext | Partial<SearchContext>) => Promise<void>;
13
+ trackClick: (resultId: string, result: any, context?: SearchContext | Partial<SearchContext>, position?: number) => Promise<void>;
14
+ trackConversion: (resultId: string, result: any, value?: number, currency?: string, context?: SearchContext | Partial<SearchContext>) => Promise<void>;
15
+ trackBatch: (events: (Partial<DataTypesEventPayload> & {
16
+ event_name?: string;
17
+ })[], context?: SearchContext) => Promise<void>;
18
+ }
19
+ export declare const useAnalytics: ({ client, enabled, }: UseAnalyticsOptions) => UseAnalyticsReturn;
20
+ //# sourceMappingURL=useAnalytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAnalytics.d.ts","sourceRoot":"","sources":["../../src/hooks/useAnalytics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAwB,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGxH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5I,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClI,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvJ,UAAU,EAAE,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,qBAAqB,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9H;AAED,eAAO,MAAM,YAAY,GAAI,sBAG1B,mBAAmB,KAAG,kBA2ExB,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * useAnalytics Hook
3
+ *
4
+ * Hook for tracking analytics events with the Seekora SDK
5
+ */
6
+ import { useCallback } from 'react';
7
+ import { log } from '@seekora-ai/ui-sdk-core';
8
+ export const useAnalytics = ({ client, enabled = true, }) => {
9
+ const trackEvent = useCallback(async (eventType, payload, context) => {
10
+ if (!enabled)
11
+ return;
12
+ try {
13
+ const eventPayload = {
14
+ event_name: eventType,
15
+ ...payload,
16
+ };
17
+ await client.trackEvent?.(eventPayload, context);
18
+ }
19
+ catch (err) {
20
+ log.error('Failed to track event:', err);
21
+ }
22
+ }, [client, enabled]);
23
+ const trackClick = useCallback(async (resultId, result, context, position) => {
24
+ if (!enabled)
25
+ return;
26
+ await trackEvent('product_click', {
27
+ clicked_item_id: resultId,
28
+ metadata: {
29
+ result: result,
30
+ ...(position !== undefined && { position }),
31
+ },
32
+ }, context);
33
+ }, [trackEvent, enabled]);
34
+ const trackConversion = useCallback(async (resultId, result, value, currency, context) => {
35
+ if (!enabled)
36
+ return;
37
+ await trackEvent('conversion', {
38
+ clicked_item_id: resultId,
39
+ value: value,
40
+ currency: currency,
41
+ metadata: {
42
+ result: result,
43
+ },
44
+ }, context);
45
+ }, [trackEvent, enabled]);
46
+ const trackBatch = useCallback(async (events, context) => {
47
+ if (!enabled || events.length === 0)
48
+ return;
49
+ try {
50
+ await client.trackEvents?.(events, context);
51
+ }
52
+ catch (err) {
53
+ log.error('Failed to track batch events:', err);
54
+ }
55
+ }, [client, enabled]);
56
+ return {
57
+ trackEvent,
58
+ trackClick,
59
+ trackConversion,
60
+ trackBatch,
61
+ };
62
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * useNaturalLanguageFilters Hook
3
+ *
4
+ * Convert natural language queries to structured filters
5
+ * Examples:
6
+ * - "red shirts under $50" → color: red, category: shirts, price: <= 50
7
+ * - "size large blue jeans" → size: L, color: blue, category: jeans
8
+ */
9
+ export interface ParsedFilter {
10
+ field: string;
11
+ value: string;
12
+ operator: '=' | '>' | '<' | '>=' | '<=' | 'contains';
13
+ confidence: number;
14
+ matchedText: string;
15
+ }
16
+ export interface NaturalLanguageResult {
17
+ /** Cleaned query with filter terms removed */
18
+ cleanedQuery: string;
19
+ /** Extracted filters */
20
+ filters: ParsedFilter[];
21
+ /** Original query */
22
+ originalQuery: string;
23
+ /** Whether any filters were detected */
24
+ hasFilters: boolean;
25
+ }
26
+ export interface NaturalLanguageFiltersOptions {
27
+ /** Custom field mappings */
28
+ fieldMappings?: Record<string, string>;
29
+ /** Custom value mappings */
30
+ valueMappings?: Record<string, Record<string, string>>;
31
+ /** Whether to auto-apply filters to search state */
32
+ autoApply?: boolean;
33
+ /** Currency symbol for price parsing */
34
+ currencySymbol?: string;
35
+ }
36
+ export declare function useNaturalLanguageFilters(options?: NaturalLanguageFiltersOptions): {
37
+ /** Parse a natural language query */
38
+ parse: (query: string) => NaturalLanguageResult;
39
+ /** Apply parsed filters to search state */
40
+ applyFilters: (filters: ParsedFilter[]) => void;
41
+ /** Parse and apply in one step */
42
+ parseAndApply: (query: string) => NaturalLanguageResult;
43
+ };
44
+ /**
45
+ * Utility to format parsed filters for display
46
+ */
47
+ export declare function formatParsedFilters(filters: ParsedFilter[]): string;
48
+ //# sourceMappingURL=useNaturalLanguageFilters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNaturalLanguageFilters.d.ts","sourceRoot":"","sources":["../../src/hooks/useNaturalLanguageFilters.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,UAAU,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,qBAAqB;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,6BAA6B;IAC5C,4BAA4B;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,4BAA4B;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAsDD,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,6BAAkC,GAC1C;IACD,qCAAqC;IACrC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,qBAAqB,CAAC;IAChD,2CAA2C;IAC3C,YAAY,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IAChD,kCAAkC;IAClC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,qBAAqB,CAAC;CACzD,CA8KA;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAInE"}