@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,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HierarchicalMenu Component
|
|
3
|
+
*
|
|
4
|
+
* Displays a hierarchical menu for nested category navigation
|
|
5
|
+
* Example: Electronics > Phones > iPhone
|
|
6
|
+
*/
|
|
7
|
+
import React, { useState, useMemo, useCallback } from 'react';
|
|
8
|
+
import { useSearchContext } from './SearchProvider';
|
|
9
|
+
import { useSearchState } from '../hooks/useSearchState';
|
|
10
|
+
import { clsx } from 'clsx';
|
|
11
|
+
export const HierarchicalMenu = ({ attributes, separator = ' > ', limit = 10, showMore = true, showMoreLimit = 20, renderItem, onSelect, className, style, theme: customTheme, rootPath = null, sortBy = 'name', items: itemsProp, }) => {
|
|
12
|
+
const { theme, stateManager } = useSearchContext();
|
|
13
|
+
const { results, refinements, addRefinement, removeRefinement } = useSearchState();
|
|
14
|
+
const hierarchicalTheme = customTheme || {};
|
|
15
|
+
const [expanded, setExpanded] = useState({});
|
|
16
|
+
// Get hierarchical facet data from results
|
|
17
|
+
const getHierarchicalData = useCallback(() => {
|
|
18
|
+
if (itemsProp)
|
|
19
|
+
return itemsProp;
|
|
20
|
+
if (!results)
|
|
21
|
+
return [];
|
|
22
|
+
// Try to find hierarchical facet data in various response formats
|
|
23
|
+
const facets = results.facets || results.facet_counts || {};
|
|
24
|
+
// Build hierarchy from flat facet data
|
|
25
|
+
const rootAttribute = attributes[0];
|
|
26
|
+
const rootFacet = facets[rootAttribute] || {};
|
|
27
|
+
const items = [];
|
|
28
|
+
Object.entries(rootFacet).forEach(([value, count]) => {
|
|
29
|
+
const isRefined = refinements.some(r => r.field === rootAttribute && r.value === value);
|
|
30
|
+
items.push({
|
|
31
|
+
label: value,
|
|
32
|
+
value,
|
|
33
|
+
count: count,
|
|
34
|
+
isRefined,
|
|
35
|
+
data: getChildItems(value, 1),
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
return items;
|
|
39
|
+
}, [results, attributes, refinements, itemsProp]);
|
|
40
|
+
// Get child items for a parent value
|
|
41
|
+
const getChildItems = useCallback((parentValue, level) => {
|
|
42
|
+
if (level >= attributes.length)
|
|
43
|
+
return [];
|
|
44
|
+
if (!results)
|
|
45
|
+
return [];
|
|
46
|
+
const facets = results.facets || results.facet_counts || {};
|
|
47
|
+
const attribute = attributes[level];
|
|
48
|
+
const facet = facets[attribute] || {};
|
|
49
|
+
const items = [];
|
|
50
|
+
Object.entries(facet).forEach(([value, count]) => {
|
|
51
|
+
// Check if this item belongs to the parent
|
|
52
|
+
if (value.startsWith(parentValue + separator)) {
|
|
53
|
+
const label = value.replace(parentValue + separator, '').split(separator)[0];
|
|
54
|
+
const isRefined = refinements.some(r => r.field === attribute && r.value === value);
|
|
55
|
+
// Only add direct children
|
|
56
|
+
const remainingPath = value.replace(parentValue + separator, '');
|
|
57
|
+
if (!remainingPath.includes(separator)) {
|
|
58
|
+
items.push({
|
|
59
|
+
label,
|
|
60
|
+
value,
|
|
61
|
+
count: count,
|
|
62
|
+
isRefined,
|
|
63
|
+
data: getChildItems(value, level + 1),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return items;
|
|
69
|
+
}, [results, attributes, separator, refinements]);
|
|
70
|
+
// Get sorted and limited items
|
|
71
|
+
const processedItems = useMemo(() => {
|
|
72
|
+
let items = getHierarchicalData();
|
|
73
|
+
// Sort
|
|
74
|
+
items = [...items].sort((a, b) => {
|
|
75
|
+
if (sortBy === 'count') {
|
|
76
|
+
return (b.count || 0) - (a.count || 0);
|
|
77
|
+
}
|
|
78
|
+
if (sortBy === 'isRefined') {
|
|
79
|
+
if (a.isRefined && !b.isRefined)
|
|
80
|
+
return -1;
|
|
81
|
+
if (!a.isRefined && b.isRefined)
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
return a.label.localeCompare(b.label);
|
|
85
|
+
});
|
|
86
|
+
return items;
|
|
87
|
+
}, [getHierarchicalData, sortBy]);
|
|
88
|
+
// Handle item click
|
|
89
|
+
const handleItemClick = useCallback((item, level) => {
|
|
90
|
+
const attribute = attributes[level] || attributes[0];
|
|
91
|
+
if (item.isRefined) {
|
|
92
|
+
// Remove this and all child refinements
|
|
93
|
+
attributes.slice(level).forEach((attr) => {
|
|
94
|
+
refinements
|
|
95
|
+
.filter(r => r.field === attr && r.value.startsWith(item.value))
|
|
96
|
+
.forEach(r => removeRefinement(r.field, r.value, false));
|
|
97
|
+
});
|
|
98
|
+
stateManager.search();
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Clear sibling refinements at this level
|
|
102
|
+
refinements
|
|
103
|
+
.filter(r => r.field === attribute && r.value !== item.value)
|
|
104
|
+
.forEach(r => removeRefinement(r.field, r.value, false));
|
|
105
|
+
// Add this refinement
|
|
106
|
+
addRefinement(attribute, item.value, true);
|
|
107
|
+
}
|
|
108
|
+
if (onSelect) {
|
|
109
|
+
onSelect(item.value, level);
|
|
110
|
+
}
|
|
111
|
+
}, [attributes, refinements, addRefinement, removeRefinement, stateManager, onSelect]);
|
|
112
|
+
// Toggle show more
|
|
113
|
+
const toggleShowMore = (level) => {
|
|
114
|
+
setExpanded(prev => ({ ...prev, [level]: !prev[level] }));
|
|
115
|
+
};
|
|
116
|
+
// Render a level of the hierarchy
|
|
117
|
+
const renderLevel = (items, level) => {
|
|
118
|
+
if (!items || items.length === 0)
|
|
119
|
+
return null;
|
|
120
|
+
const isExpanded = expanded[level] || false;
|
|
121
|
+
const displayLimit = isExpanded ? showMoreLimit : limit;
|
|
122
|
+
const displayItems = items.slice(0, displayLimit);
|
|
123
|
+
const hasMore = items.length > displayLimit;
|
|
124
|
+
return (React.createElement("ul", { className: hierarchicalTheme.list, style: {
|
|
125
|
+
listStyle: 'none',
|
|
126
|
+
margin: 0,
|
|
127
|
+
padding: level > 0 ? `0 0 0 ${theme.spacing.medium}` : 0,
|
|
128
|
+
} },
|
|
129
|
+
displayItems.map((item, index) => (React.createElement("li", { key: item.value, className: clsx(hierarchicalTheme.item, item.isRefined && hierarchicalTheme.itemSelected, item.data && item.data.length > 0 && hierarchicalTheme.itemParent), style: {
|
|
130
|
+
padding: `${theme.spacing.small} 0`,
|
|
131
|
+
} }, renderItem ? (renderItem(item, level)) : (React.createElement(React.Fragment, null,
|
|
132
|
+
React.createElement("button", { type: "button", onClick: () => handleItemClick(item, level), className: hierarchicalTheme.link, style: {
|
|
133
|
+
display: 'flex',
|
|
134
|
+
alignItems: 'center',
|
|
135
|
+
gap: theme.spacing.small,
|
|
136
|
+
width: '100%',
|
|
137
|
+
padding: 0,
|
|
138
|
+
background: 'none',
|
|
139
|
+
border: 'none',
|
|
140
|
+
cursor: 'pointer',
|
|
141
|
+
textAlign: 'left',
|
|
142
|
+
fontFamily: 'inherit',
|
|
143
|
+
fontSize: theme.typography.fontSize.small,
|
|
144
|
+
color: item.isRefined ? theme.colors.primary : theme.colors.text,
|
|
145
|
+
fontWeight: item.isRefined ? 600 : 400,
|
|
146
|
+
} },
|
|
147
|
+
React.createElement("span", { className: hierarchicalTheme.label, style: { flex: 1 } }, item.label),
|
|
148
|
+
item.count !== undefined && (React.createElement("span", { className: hierarchicalTheme.count, style: {
|
|
149
|
+
color: theme.colors.textSecondary,
|
|
150
|
+
fontSize: theme.typography.fontSize.small,
|
|
151
|
+
} }, item.count))),
|
|
152
|
+
item.isRefined && item.data && item.data.length > 0 && (renderLevel(item.data, level + 1))))))),
|
|
153
|
+
showMore && hasMore && (React.createElement("li", null,
|
|
154
|
+
React.createElement("button", { type: "button", onClick: () => toggleShowMore(level), className: hierarchicalTheme.showMore, style: {
|
|
155
|
+
padding: `${theme.spacing.small} 0`,
|
|
156
|
+
background: 'none',
|
|
157
|
+
border: 'none',
|
|
158
|
+
cursor: 'pointer',
|
|
159
|
+
fontSize: theme.typography.fontSize.small,
|
|
160
|
+
color: theme.colors.primary,
|
|
161
|
+
textDecoration: 'underline',
|
|
162
|
+
} }, isExpanded ? 'Show less' : `Show ${items.length - displayLimit} more`)))));
|
|
163
|
+
};
|
|
164
|
+
if (processedItems.length === 0) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
return (React.createElement("div", { className: clsx(hierarchicalTheme.root, className), style: style }, renderLevel(processedItems, 0)));
|
|
168
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Highlight Component
|
|
3
|
+
*
|
|
4
|
+
* Renders highlighted search result text with matching terms emphasized
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { HighlightPart } from '@seekora-ai/ui-sdk-core';
|
|
8
|
+
export interface HighlightTheme {
|
|
9
|
+
root?: string;
|
|
10
|
+
highlighted?: string;
|
|
11
|
+
nonHighlighted?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface HighlightProps {
|
|
14
|
+
/** The search hit containing highlight data */
|
|
15
|
+
hit: Record<string, any>;
|
|
16
|
+
/** The attribute to highlight */
|
|
17
|
+
attribute: string;
|
|
18
|
+
/** Custom className */
|
|
19
|
+
className?: string;
|
|
20
|
+
/** Custom styles */
|
|
21
|
+
style?: React.CSSProperties;
|
|
22
|
+
/** Custom theme */
|
|
23
|
+
theme?: HighlightTheme;
|
|
24
|
+
/** Tag name for root element (default: 'span') */
|
|
25
|
+
tagName?: keyof JSX.IntrinsicElements;
|
|
26
|
+
/** Custom render for highlighted parts */
|
|
27
|
+
renderHighlighted?: (part: HighlightPart, index: number) => React.ReactNode;
|
|
28
|
+
/** Custom render for non-highlighted parts */
|
|
29
|
+
renderNonHighlighted?: (part: HighlightPart, index: number) => React.ReactNode;
|
|
30
|
+
/** Fallback query for client-side highlighting (when server highlight not available) */
|
|
31
|
+
query?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare const Highlight: React.FC<HighlightProps>;
|
|
34
|
+
/**
|
|
35
|
+
* Snippet Component
|
|
36
|
+
*
|
|
37
|
+
* Renders a truncated, highlighted excerpt from search results
|
|
38
|
+
*/
|
|
39
|
+
export interface SnippetTheme extends HighlightTheme {
|
|
40
|
+
ellipsis?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface SnippetProps extends Omit<HighlightProps, 'theme'> {
|
|
43
|
+
/** Maximum length of snippet (default: 100) */
|
|
44
|
+
maxLength?: number;
|
|
45
|
+
/** Ellipsis to use when truncating (default: '...') */
|
|
46
|
+
ellipsis?: string;
|
|
47
|
+
/** Custom theme */
|
|
48
|
+
theme?: SnippetTheme;
|
|
49
|
+
}
|
|
50
|
+
export declare const Snippet: React.FC<SnippetProps>;
|
|
51
|
+
//# sourceMappingURL=Highlight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Highlight.d.ts","sourceRoot":"","sources":["../../src/components/Highlight.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,EAIL,aAAa,EACd,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;IACtC,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5E,8CAA8C;IAC9C,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IAC/E,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA8E9C,CAAC;AAEF;;;;GAIG;AAEH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC;IACjE,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CA2G1C,CAAC"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Highlight Component
|
|
3
|
+
*
|
|
4
|
+
* Renders highlighted search result text with matching terms emphasized
|
|
5
|
+
*/
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
|
+
import { useSearchContext } from './SearchProvider';
|
|
8
|
+
import { getHighlightedValue, parseHighlightedParts, parseQueryHighlightParts, } from '@seekora-ai/ui-sdk-core';
|
|
9
|
+
export const Highlight = ({ hit, attribute, className, style, theme: customTheme, tagName: Tag = 'span', renderHighlighted, renderNonHighlighted, query, }) => {
|
|
10
|
+
const { theme } = useSearchContext();
|
|
11
|
+
const highlightTheme = customTheme || {};
|
|
12
|
+
// Get highlighted value from hit
|
|
13
|
+
const highlightedValue = useMemo(() => {
|
|
14
|
+
return getHighlightedValue({
|
|
15
|
+
hit,
|
|
16
|
+
attribute,
|
|
17
|
+
preTag: '<mark>',
|
|
18
|
+
postTag: '</mark>',
|
|
19
|
+
});
|
|
20
|
+
}, [hit, attribute]);
|
|
21
|
+
// Parse into parts
|
|
22
|
+
const parts = useMemo(() => {
|
|
23
|
+
// First try server-provided highlights
|
|
24
|
+
const serverParts = parseHighlightedParts(highlightedValue, '<mark>', '</mark>');
|
|
25
|
+
// If no highlights found and we have a query, do client-side highlighting
|
|
26
|
+
if (serverParts.every((p) => !p.isHighlighted) && query) {
|
|
27
|
+
const plainValue = hit[attribute] || highlightedValue;
|
|
28
|
+
return parseQueryHighlightParts(String(plainValue), query);
|
|
29
|
+
}
|
|
30
|
+
return serverParts;
|
|
31
|
+
}, [highlightedValue, query, hit, attribute]);
|
|
32
|
+
// Default highlight style
|
|
33
|
+
const defaultHighlightStyle = {
|
|
34
|
+
backgroundColor: theme.colors.warning || '#fff59d',
|
|
35
|
+
fontWeight: theme.typography.fontWeight?.semibold || 600,
|
|
36
|
+
padding: '0 2px',
|
|
37
|
+
borderRadius: '2px',
|
|
38
|
+
};
|
|
39
|
+
// Render parts
|
|
40
|
+
const renderedParts = parts.map((part, index) => {
|
|
41
|
+
if (part.isHighlighted) {
|
|
42
|
+
if (renderHighlighted) {
|
|
43
|
+
return React.createElement(React.Fragment, { key: index }, renderHighlighted(part, index));
|
|
44
|
+
}
|
|
45
|
+
return (React.createElement("mark", { key: index, className: highlightTheme.highlighted, style: defaultHighlightStyle }, part.value));
|
|
46
|
+
}
|
|
47
|
+
if (renderNonHighlighted) {
|
|
48
|
+
return React.createElement(React.Fragment, { key: index }, renderNonHighlighted(part, index));
|
|
49
|
+
}
|
|
50
|
+
return (React.createElement("span", { key: index, className: highlightTheme.nonHighlighted }, part.value));
|
|
51
|
+
});
|
|
52
|
+
return (React.createElement(Tag, { className: className || highlightTheme.root, style: style }, renderedParts));
|
|
53
|
+
};
|
|
54
|
+
export const Snippet = ({ hit, attribute, maxLength = 100, ellipsis = '...', className, style, theme: customTheme, tagName: Tag = 'span', renderHighlighted, renderNonHighlighted, query, }) => {
|
|
55
|
+
const { theme } = useSearchContext();
|
|
56
|
+
const snippetTheme = customTheme || {};
|
|
57
|
+
// Get snippet or truncated highlighted value
|
|
58
|
+
const snippetValue = useMemo(() => {
|
|
59
|
+
// Check for server-provided snippet
|
|
60
|
+
const snippetResult = hit._snippetResult?.[attribute]?.value ||
|
|
61
|
+
hit.snippet_result?.[attribute]?.value ||
|
|
62
|
+
hit._snippet?.[attribute] ||
|
|
63
|
+
hit.snippet?.[attribute];
|
|
64
|
+
if (snippetResult) {
|
|
65
|
+
return snippetResult
|
|
66
|
+
.replace(/<em>/g, '<mark>')
|
|
67
|
+
.replace(/<\/em>/g, '</mark>');
|
|
68
|
+
}
|
|
69
|
+
// Fall back to highlighted value, truncated
|
|
70
|
+
let text = getHighlightedValue({
|
|
71
|
+
hit,
|
|
72
|
+
attribute,
|
|
73
|
+
preTag: '<mark>',
|
|
74
|
+
postTag: '</mark>',
|
|
75
|
+
});
|
|
76
|
+
// Remove tags for length calculation
|
|
77
|
+
const plainText = text.replace(/<\/?mark>/g, '');
|
|
78
|
+
if (plainText.length > maxLength) {
|
|
79
|
+
// Need to truncate while preserving tags
|
|
80
|
+
const truncated = truncateWithTags(text, maxLength);
|
|
81
|
+
return truncated + ellipsis;
|
|
82
|
+
}
|
|
83
|
+
return text;
|
|
84
|
+
}, [hit, attribute, maxLength, ellipsis]);
|
|
85
|
+
// Parse into parts
|
|
86
|
+
const parts = useMemo(() => {
|
|
87
|
+
const serverParts = parseHighlightedParts(snippetValue, '<mark>', '</mark>');
|
|
88
|
+
if (serverParts.every((p) => !p.isHighlighted) && query) {
|
|
89
|
+
const plainValue = hit[attribute] || snippetValue;
|
|
90
|
+
let text = String(plainValue);
|
|
91
|
+
if (text.length > maxLength) {
|
|
92
|
+
text = text.substring(0, maxLength) + ellipsis;
|
|
93
|
+
}
|
|
94
|
+
return parseQueryHighlightParts(text, query);
|
|
95
|
+
}
|
|
96
|
+
return serverParts;
|
|
97
|
+
}, [snippetValue, query, hit, attribute, maxLength, ellipsis]);
|
|
98
|
+
// Default highlight style
|
|
99
|
+
const defaultHighlightStyle = {
|
|
100
|
+
backgroundColor: theme.colors.warning || '#fff59d',
|
|
101
|
+
fontWeight: theme.typography.fontWeight?.semibold || 600,
|
|
102
|
+
padding: '0 2px',
|
|
103
|
+
borderRadius: '2px',
|
|
104
|
+
};
|
|
105
|
+
// Render parts
|
|
106
|
+
const renderedParts = parts.map((part, index) => {
|
|
107
|
+
if (part.isHighlighted) {
|
|
108
|
+
if (renderHighlighted) {
|
|
109
|
+
return React.createElement(React.Fragment, { key: index }, renderHighlighted(part, index));
|
|
110
|
+
}
|
|
111
|
+
return (React.createElement("mark", { key: index, className: snippetTheme.highlighted, style: defaultHighlightStyle }, part.value));
|
|
112
|
+
}
|
|
113
|
+
if (renderNonHighlighted) {
|
|
114
|
+
return React.createElement(React.Fragment, { key: index }, renderNonHighlighted(part, index));
|
|
115
|
+
}
|
|
116
|
+
return (React.createElement("span", { key: index, className: snippetTheme.nonHighlighted }, part.value));
|
|
117
|
+
});
|
|
118
|
+
return (React.createElement(Tag, { className: className || snippetTheme.root, style: style }, renderedParts));
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Truncate text while preserving HTML tags
|
|
122
|
+
*/
|
|
123
|
+
function truncateWithTags(text, maxLength) {
|
|
124
|
+
let visibleLength = 0;
|
|
125
|
+
let result = '';
|
|
126
|
+
let inTag = false;
|
|
127
|
+
for (let i = 0; i < text.length; i++) {
|
|
128
|
+
const char = text[i];
|
|
129
|
+
if (char === '<') {
|
|
130
|
+
inTag = true;
|
|
131
|
+
result += char;
|
|
132
|
+
}
|
|
133
|
+
else if (char === '>') {
|
|
134
|
+
inTag = false;
|
|
135
|
+
result += char;
|
|
136
|
+
}
|
|
137
|
+
else if (inTag) {
|
|
138
|
+
result += char;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
if (visibleLength >= maxLength) {
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
result += char;
|
|
145
|
+
visibleLength++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Close any unclosed tags
|
|
149
|
+
const openTags = result.match(/<mark>/g)?.length || 0;
|
|
150
|
+
const closeTags = result.match(/<\/mark>/g)?.length || 0;
|
|
151
|
+
for (let i = 0; i < openTags - closeTags; i++) {
|
|
152
|
+
result += '</mark>';
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HitsPerPage Component
|
|
3
|
+
*
|
|
4
|
+
* A dropdown to let users change the number of displayed hits per page
|
|
5
|
+
* Integrates with SearchStateManager for automatic state sync
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
export interface HitsPerPageItem {
|
|
9
|
+
value: number;
|
|
10
|
+
label: string;
|
|
11
|
+
default?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface HitsPerPageTheme {
|
|
14
|
+
root?: string;
|
|
15
|
+
select?: string;
|
|
16
|
+
option?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface HitsPerPageProps {
|
|
19
|
+
/** Available items per page options */
|
|
20
|
+
items: HitsPerPageItem[];
|
|
21
|
+
/** Callback when hits per page changes */
|
|
22
|
+
onHitsPerPageChange?: (value: number) => void;
|
|
23
|
+
/** Custom render function for select */
|
|
24
|
+
renderSelect?: (props: {
|
|
25
|
+
value: number;
|
|
26
|
+
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
|
27
|
+
items: HitsPerPageItem[];
|
|
28
|
+
}) => React.ReactNode;
|
|
29
|
+
/** Custom className */
|
|
30
|
+
className?: string;
|
|
31
|
+
/** Custom styles */
|
|
32
|
+
style?: React.CSSProperties;
|
|
33
|
+
/** Custom theme */
|
|
34
|
+
theme?: HitsPerPageTheme;
|
|
35
|
+
/** Label text before the select */
|
|
36
|
+
label?: string;
|
|
37
|
+
/** Whether to sync with SearchStateManager (default: true) */
|
|
38
|
+
syncWithState?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare const HitsPerPage: React.FC<HitsPerPageProps>;
|
|
41
|
+
//# sourceMappingURL=HitsPerPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HitsPerPage.d.ts","sourceRoot":"","sources":["../../src/components/HitsPerPage.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAoB,MAAM,OAAO,CAAC;AAKzC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,0CAA0C;IAC1C,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,wCAAwC;IACxC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;QAC5D,KAAK,EAAE,eAAe,EAAE,CAAC;KAC1B,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA8GlD,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HitsPerPage Component
|
|
3
|
+
*
|
|
4
|
+
* A dropdown to let users change the number of displayed hits per page
|
|
5
|
+
* Integrates with SearchStateManager for automatic state sync
|
|
6
|
+
*/
|
|
7
|
+
import React, { useEffect } from 'react';
|
|
8
|
+
import { useSearchContext } from './SearchProvider';
|
|
9
|
+
import { useSearchState } from '../hooks/useSearchState';
|
|
10
|
+
import { clsx } from 'clsx';
|
|
11
|
+
export const HitsPerPage = ({ items, onHitsPerPageChange, renderSelect, className, style, theme: customTheme, label = 'Show', syncWithState = true, }) => {
|
|
12
|
+
const { theme } = useSearchContext();
|
|
13
|
+
const { itemsPerPage, setPage } = useSearchState();
|
|
14
|
+
const { stateManager } = useSearchContext();
|
|
15
|
+
const hitsPerPageTheme = customTheme || {};
|
|
16
|
+
// Find the default item or first item
|
|
17
|
+
const defaultItem = items.find(i => i.default) || items[0];
|
|
18
|
+
const [internalValue, setInternalValue] = React.useState(defaultItem?.value || 10);
|
|
19
|
+
// Initialize StateManager on mount if default is set
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (syncWithState && defaultItem && itemsPerPage !== defaultItem.value) {
|
|
22
|
+
stateManager.setItemsPerPage(defaultItem.value, false);
|
|
23
|
+
}
|
|
24
|
+
}, []);
|
|
25
|
+
// Determine the current value from StateManager or internal
|
|
26
|
+
const value = syncWithState ? itemsPerPage : internalValue;
|
|
27
|
+
const handleChange = (e) => {
|
|
28
|
+
const newValue = parseInt(e.target.value, 10);
|
|
29
|
+
setInternalValue(newValue);
|
|
30
|
+
// Update StateManager
|
|
31
|
+
if (syncWithState) {
|
|
32
|
+
stateManager.setItemsPerPage(newValue, false);
|
|
33
|
+
// Reset to page 1 when changing items per page
|
|
34
|
+
setPage(1, true);
|
|
35
|
+
}
|
|
36
|
+
// Call callback
|
|
37
|
+
if (onHitsPerPageChange) {
|
|
38
|
+
onHitsPerPageChange(newValue);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
// Custom render function
|
|
42
|
+
if (renderSelect) {
|
|
43
|
+
return (React.createElement("div", { className: clsx(hitsPerPageTheme.root, className), style: style }, renderSelect({
|
|
44
|
+
value,
|
|
45
|
+
onChange: handleChange,
|
|
46
|
+
items,
|
|
47
|
+
})));
|
|
48
|
+
}
|
|
49
|
+
return (React.createElement("div", { className: clsx(hitsPerPageTheme.root, className), style: {
|
|
50
|
+
display: 'flex',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
gap: theme.spacing.small,
|
|
53
|
+
...style,
|
|
54
|
+
} },
|
|
55
|
+
label && (React.createElement("label", { style: {
|
|
56
|
+
fontSize: theme.typography.fontSize.medium,
|
|
57
|
+
color: theme.colors.text,
|
|
58
|
+
} }, label)),
|
|
59
|
+
React.createElement("select", { value: value, onChange: handleChange, className: hitsPerPageTheme.select, style: {
|
|
60
|
+
padding: theme.spacing.small,
|
|
61
|
+
paddingRight: theme.spacing.medium,
|
|
62
|
+
fontSize: theme.typography.fontSize.medium,
|
|
63
|
+
border: `1px solid ${theme.colors.border}`,
|
|
64
|
+
borderRadius: typeof theme.borderRadius === 'string'
|
|
65
|
+
? theme.borderRadius
|
|
66
|
+
: theme.borderRadius.medium,
|
|
67
|
+
backgroundColor: theme.colors.background,
|
|
68
|
+
color: theme.colors.text,
|
|
69
|
+
cursor: 'pointer',
|
|
70
|
+
outline: 'none',
|
|
71
|
+
}, "aria-label": "Results per page" }, items.map((item) => (React.createElement("option", { key: item.value, value: item.value, className: hitsPerPageTheme.option }, item.label))))));
|
|
72
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfiniteHits Component
|
|
3
|
+
*
|
|
4
|
+
* Displays search results with infinite scroll or "Show More" button
|
|
5
|
+
* Accumulates results as user loads more pages
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import type { ResultItem, FieldMapping } from '@seekora-ai/ui-sdk-types';
|
|
9
|
+
export interface InfiniteHitsTheme {
|
|
10
|
+
root?: string;
|
|
11
|
+
list?: string;
|
|
12
|
+
item?: string;
|
|
13
|
+
loadMore?: string;
|
|
14
|
+
loadMoreDisabled?: string;
|
|
15
|
+
loading?: string;
|
|
16
|
+
empty?: string;
|
|
17
|
+
sentinel?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface InfiniteHitsProps {
|
|
20
|
+
/** Custom render function for each hit */
|
|
21
|
+
renderHit?: (hit: ResultItem, index: number) => React.ReactNode;
|
|
22
|
+
/** Custom render for empty state */
|
|
23
|
+
renderEmpty?: () => React.ReactNode;
|
|
24
|
+
/** Custom render for loading state */
|
|
25
|
+
renderLoading?: () => React.ReactNode;
|
|
26
|
+
/** Custom render for "Show More" button */
|
|
27
|
+
renderShowMore?: (props: {
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
isLastPage: boolean;
|
|
30
|
+
onClick: () => void;
|
|
31
|
+
}) => React.ReactNode;
|
|
32
|
+
/** Whether to show the "Show More" button (default: true) */
|
|
33
|
+
showMoreButton?: boolean;
|
|
34
|
+
/** Whether to use infinite scroll with IntersectionObserver (default: false) */
|
|
35
|
+
useInfiniteScroll?: boolean;
|
|
36
|
+
/** Threshold for IntersectionObserver (default: 0.1) */
|
|
37
|
+
scrollThreshold?: number;
|
|
38
|
+
/** Field mapping for extracting data from hits */
|
|
39
|
+
fieldMapping?: FieldMapping;
|
|
40
|
+
/** Text for "Show More" button */
|
|
41
|
+
showMoreLabel?: string;
|
|
42
|
+
/** Text for loading state */
|
|
43
|
+
loadingLabel?: string;
|
|
44
|
+
/** Callback when a hit is clicked */
|
|
45
|
+
onHitClick?: (hit: ResultItem, index: number) => void;
|
|
46
|
+
/** Custom className */
|
|
47
|
+
className?: string;
|
|
48
|
+
/** Custom styles */
|
|
49
|
+
style?: React.CSSProperties;
|
|
50
|
+
/** Custom theme */
|
|
51
|
+
theme?: InfiniteHitsTheme;
|
|
52
|
+
/** Whether to sync with SearchStateManager (default: true) */
|
|
53
|
+
syncWithState?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export declare const InfiniteHits: React.FC<InfiniteHitsProps>;
|
|
56
|
+
//# sourceMappingURL=InfiniteHits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InfiniteHits.d.ts","sourceRoot":"","sources":["../../src/components/InfiniteHits.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AAIjF,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAEzE,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IAChE,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACpC,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACtC,2CAA2C;IAC3C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,OAAO,CAAC;QACpB,OAAO,EAAE,MAAM,IAAI,CAAC;KACrB,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAoSpD,CAAC"}
|