@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.
- package/dist/components/Breadcrumb.d.ts +43 -0
- package/dist/components/Breadcrumb.d.ts.map +1 -0
- package/dist/components/Breadcrumb.js +119 -0
- package/dist/components/ClearRefinements.d.ts +42 -0
- package/dist/components/ClearRefinements.d.ts.map +1 -0
- package/dist/components/ClearRefinements.js +80 -0
- package/dist/components/CurrentRefinements.d.ts +41 -0
- package/dist/components/CurrentRefinements.d.ts.map +1 -0
- package/dist/components/CurrentRefinements.js +83 -0
- package/dist/components/Facets.d.ts +53 -0
- package/dist/components/Facets.d.ts.map +1 -0
- package/dist/components/Facets.js +195 -0
- package/dist/components/FederatedDropdown.d.ts +92 -0
- package/dist/components/FederatedDropdown.d.ts.map +1 -0
- package/dist/components/FederatedDropdown.js +510 -0
- package/dist/components/HierarchicalMenu.d.ts +55 -0
- package/dist/components/HierarchicalMenu.d.ts.map +1 -0
- package/dist/components/HierarchicalMenu.js +168 -0
- package/dist/components/Highlight.d.ts +51 -0
- package/dist/components/Highlight.d.ts.map +1 -0
- package/dist/components/Highlight.js +155 -0
- package/dist/components/HitsPerPage.d.ts +41 -0
- package/dist/components/HitsPerPage.d.ts.map +1 -0
- package/dist/components/HitsPerPage.js +72 -0
- package/dist/components/InfiniteHits.d.ts +56 -0
- package/dist/components/InfiniteHits.d.ts.map +1 -0
- package/dist/components/InfiniteHits.js +181 -0
- package/dist/components/MobileFilters.d.ts +71 -0
- package/dist/components/MobileFilters.d.ts.map +1 -0
- package/dist/components/MobileFilters.js +242 -0
- package/dist/components/Pagination.d.ts +44 -0
- package/dist/components/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination.js +142 -0
- package/dist/components/QuerySuggestions.d.ts +38 -0
- package/dist/components/QuerySuggestions.d.ts.map +1 -0
- package/dist/components/QuerySuggestions.js +86 -0
- package/dist/components/QuerySuggestionsDropdown.d.ts +86 -0
- package/dist/components/QuerySuggestionsDropdown.d.ts.map +1 -0
- package/dist/components/QuerySuggestionsDropdown.js +395 -0
- package/dist/components/RangeInput.d.ts +58 -0
- package/dist/components/RangeInput.d.ts.map +1 -0
- package/dist/components/RangeInput.js +203 -0
- package/dist/components/RangeSlider.d.ts +51 -0
- package/dist/components/RangeSlider.d.ts.map +1 -0
- package/dist/components/RangeSlider.js +193 -0
- package/dist/components/Recommendations.d.ts +90 -0
- package/dist/components/Recommendations.d.ts.map +1 -0
- package/dist/components/Recommendations.js +270 -0
- package/dist/components/RichQuerySuggestions.d.ts +77 -0
- package/dist/components/RichQuerySuggestions.d.ts.map +1 -0
- package/dist/components/RichQuerySuggestions.js +492 -0
- package/dist/components/SearchBar.d.ts +40 -0
- package/dist/components/SearchBar.d.ts.map +1 -0
- package/dist/components/SearchBar.js +217 -0
- package/dist/components/SearchBarWithSuggestions.d.ts +99 -0
- package/dist/components/SearchBarWithSuggestions.d.ts.map +1 -0
- package/dist/components/SearchBarWithSuggestions.js +275 -0
- package/dist/components/SearchLayout.d.ts +35 -0
- package/dist/components/SearchLayout.d.ts.map +1 -0
- package/dist/components/SearchLayout.js +56 -0
- package/dist/components/SearchProvider.d.ts +28 -0
- package/dist/components/SearchProvider.d.ts.map +1 -0
- package/dist/components/SearchProvider.js +43 -0
- package/dist/components/SearchResults.d.ts +51 -0
- package/dist/components/SearchResults.d.ts.map +1 -0
- package/dist/components/SearchResults.js +485 -0
- package/dist/components/SortBy.d.ts +44 -0
- package/dist/components/SortBy.d.ts.map +1 -0
- package/dist/components/SortBy.js +61 -0
- package/dist/components/Stats.d.ts +37 -0
- package/dist/components/Stats.d.ts.map +1 -0
- package/dist/components/Stats.js +52 -0
- package/dist/components/suggestions/AmazonDropdown.d.ts +30 -0
- package/dist/components/suggestions/AmazonDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/AmazonDropdown.js +529 -0
- package/dist/components/suggestions/GoogleDropdown.d.ts +31 -0
- package/dist/components/suggestions/GoogleDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/GoogleDropdown.js +370 -0
- package/dist/components/suggestions/MinimalDropdown.d.ts +24 -0
- package/dist/components/suggestions/MinimalDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/MinimalDropdown.js +314 -0
- package/dist/components/suggestions/MobileSheetDropdown.d.ts +31 -0
- package/dist/components/suggestions/MobileSheetDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/MobileSheetDropdown.js +485 -0
- package/dist/components/suggestions/PinterestDropdown.d.ts +29 -0
- package/dist/components/suggestions/PinterestDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/PinterestDropdown.js +450 -0
- package/dist/components/suggestions/ShopifyDropdown.d.ts +27 -0
- package/dist/components/suggestions/ShopifyDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/ShopifyDropdown.js +451 -0
- package/dist/components/suggestions/SpotlightDropdown.d.ts +33 -0
- package/dist/components/suggestions/SpotlightDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/SpotlightDropdown.js +547 -0
- package/dist/components/suggestions/SuggestionSearchBar.d.ts +123 -0
- package/dist/components/suggestions/SuggestionSearchBar.d.ts.map +1 -0
- package/dist/components/suggestions/SuggestionSearchBar.js +652 -0
- package/dist/components/suggestions/index.d.ts +37 -0
- package/dist/components/suggestions/index.d.ts.map +1 -0
- package/dist/components/suggestions/index.js +59 -0
- package/dist/components/suggestions/styles/index.d.ts +11 -0
- package/dist/components/suggestions/styles/index.d.ts.map +1 -0
- package/dist/components/suggestions/styles/index.js +289 -0
- package/dist/components/suggestions/styles/responsive.d.ts +107 -0
- package/dist/components/suggestions/styles/responsive.d.ts.map +1 -0
- package/dist/components/suggestions/styles/responsive.js +237 -0
- package/dist/components/suggestions/types.d.ts +489 -0
- package/dist/components/suggestions/types.d.ts.map +1 -0
- package/dist/components/suggestions/types.js +6 -0
- package/dist/components/suggestions/utils.d.ts +213 -0
- package/dist/components/suggestions/utils.d.ts.map +1 -0
- package/dist/components/suggestions/utils.js +514 -0
- package/dist/hooks/useAnalytics.d.ts +20 -0
- package/dist/hooks/useAnalytics.d.ts.map +1 -0
- package/dist/hooks/useAnalytics.js +62 -0
- package/dist/hooks/useNaturalLanguageFilters.d.ts +48 -0
- package/dist/hooks/useNaturalLanguageFilters.d.ts.map +1 -0
- package/dist/hooks/useNaturalLanguageFilters.js +221 -0
- package/dist/hooks/useQuerySuggestions.d.ts +21 -0
- package/dist/hooks/useQuerySuggestions.d.ts.map +1 -0
- package/dist/hooks/useQuerySuggestions.js +68 -0
- package/dist/hooks/useQuerySuggestionsEnhanced.d.ts +114 -0
- package/dist/hooks/useQuerySuggestionsEnhanced.d.ts.map +1 -0
- package/dist/hooks/useQuerySuggestionsEnhanced.js +376 -0
- package/dist/hooks/useSearchState.d.ts +35 -0
- package/dist/hooks/useSearchState.d.ts.map +1 -0
- package/dist/hooks/useSearchState.js +68 -0
- package/dist/hooks/useSeekoraSearch.d.ts +20 -0
- package/dist/hooks/useSeekoraSearch.d.ts.map +1 -0
- package/dist/hooks/useSeekoraSearch.js +63 -0
- package/dist/hooks/useSmartSuggestions.d.ts +55 -0
- package/dist/hooks/useSmartSuggestions.d.ts.map +1 -0
- package/dist/hooks/useSmartSuggestions.js +236 -0
- package/dist/hooks/useSuggestionsAnalytics.d.ts +91 -0
- package/dist/hooks/useSuggestionsAnalytics.d.ts.map +1 -0
- package/dist/hooks/useSuggestionsAnalytics.js +226 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.umd.js +1 -0
- package/dist/src/index.d.ts +2849 -0
- package/dist/src/index.esm.js +11679 -0
- package/dist/src/index.esm.js.map +1 -0
- package/dist/src/index.js +11761 -0
- package/dist/src/index.js.map +1 -0
- package/dist/themes/createTheme.d.ts +8 -0
- package/dist/themes/createTheme.d.ts.map +1 -0
- package/dist/themes/createTheme.js +10 -0
- package/dist/themes/dark.d.ts +6 -0
- package/dist/themes/dark.d.ts.map +1 -0
- package/dist/themes/dark.js +34 -0
- package/dist/themes/default.d.ts +6 -0
- package/dist/themes/default.d.ts.map +1 -0
- package/dist/themes/default.js +71 -0
- package/dist/themes/mergeThemes.d.ts +7 -0
- package/dist/themes/mergeThemes.d.ts.map +1 -0
- package/dist/themes/mergeThemes.js +6 -0
- package/dist/themes/minimal.d.ts +6 -0
- package/dist/themes/minimal.d.ts.map +1 -0
- package/dist/themes/minimal.js +34 -0
- package/dist/themes/suggestions.d.ts +216 -0
- package/dist/themes/suggestions.d.ts.map +1 -0
- package/dist/themes/suggestions.js +546 -0
- package/dist/themes/types.d.ts +7 -0
- package/dist/themes/types.d.ts.map +1 -0
- package/dist/themes/types.js +6 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- 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
|
+
'&': '&',
|
|
129
|
+
'<': '<',
|
|
130
|
+
'>': '>',
|
|
131
|
+
'"': '"',
|
|
132
|
+
"'": ''',
|
|
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"}
|