@seekora-ai/ui-sdk-react 0.2.5 → 0.2.8

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 (104) hide show
  1. package/dist/components/SearchBarWithSuggestions.d.ts +2 -0
  2. package/dist/components/SearchBarWithSuggestions.d.ts.map +1 -1
  3. package/dist/components/SearchBarWithSuggestions.js +2 -2
  4. package/dist/components/primitives/ImageDisplay.d.ts +19 -0
  5. package/dist/components/primitives/ImageDisplay.d.ts.map +1 -0
  6. package/dist/components/primitives/ImageDisplay.js +74 -0
  7. package/dist/components/primitives/index.d.ts +2 -0
  8. package/dist/components/primitives/index.d.ts.map +1 -0
  9. package/dist/components/primitives/index.js +1 -0
  10. package/dist/components/product-page/ProductGallery.d.ts +19 -0
  11. package/dist/components/product-page/ProductGallery.d.ts.map +1 -0
  12. package/dist/components/product-page/ProductGallery.js +13 -0
  13. package/dist/components/product-page/ProductInfo.d.ts +21 -0
  14. package/dist/components/product-page/ProductInfo.d.ts.map +1 -0
  15. package/dist/components/product-page/ProductInfo.js +19 -0
  16. package/dist/components/product-page/ProductRecommendations.d.ts +21 -0
  17. package/dist/components/product-page/ProductRecommendations.d.ts.map +1 -0
  18. package/dist/components/product-page/ProductRecommendations.js +17 -0
  19. package/dist/components/product-page/index.d.ts +4 -0
  20. package/dist/components/product-page/index.d.ts.map +1 -0
  21. package/dist/components/product-page/index.js +3 -0
  22. package/dist/components/section-primitives/SectionError.d.ts +11 -0
  23. package/dist/components/section-primitives/SectionError.d.ts.map +1 -0
  24. package/dist/components/section-primitives/SectionError.js +13 -0
  25. package/dist/components/section-primitives/SectionItemGrid.d.ts +18 -0
  26. package/dist/components/section-primitives/SectionItemGrid.d.ts.map +1 -0
  27. package/dist/components/section-primitives/SectionItemGrid.js +16 -0
  28. package/dist/components/section-primitives/SectionLoading.d.ts +11 -0
  29. package/dist/components/section-primitives/SectionLoading.d.ts.map +1 -0
  30. package/dist/components/section-primitives/SectionLoading.js +11 -0
  31. package/dist/components/section-primitives/SectionSearchContext.d.ts +17 -0
  32. package/dist/components/section-primitives/SectionSearchContext.d.ts.map +1 -0
  33. package/dist/components/section-primitives/SectionSearchContext.js +17 -0
  34. package/dist/components/section-primitives/SectionSearchProvider.d.ts +23 -0
  35. package/dist/components/section-primitives/SectionSearchProvider.d.ts.map +1 -0
  36. package/dist/components/section-primitives/SectionSearchProvider.js +105 -0
  37. package/dist/components/section-primitives/index.d.ts +6 -0
  38. package/dist/components/section-primitives/index.d.ts.map +1 -0
  39. package/dist/components/section-primitives/index.js +5 -0
  40. package/dist/components/suggestions-primitives/CategoriesTabs.d.ts +13 -0
  41. package/dist/components/suggestions-primitives/CategoriesTabs.d.ts.map +1 -0
  42. package/dist/components/suggestions-primitives/CategoriesTabs.js +35 -0
  43. package/dist/components/suggestions-primitives/DropdownPanel.d.ts +24 -0
  44. package/dist/components/suggestions-primitives/DropdownPanel.d.ts.map +1 -0
  45. package/dist/components/suggestions-primitives/DropdownPanel.js +54 -0
  46. package/dist/components/suggestions-primitives/ItemCard.d.ts +39 -0
  47. package/dist/components/suggestions-primitives/ItemCard.d.ts.map +1 -0
  48. package/dist/components/suggestions-primitives/ItemCard.js +52 -0
  49. package/dist/components/suggestions-primitives/ItemGrid.d.ts +28 -0
  50. package/dist/components/suggestions-primitives/ItemGrid.d.ts.map +1 -0
  51. package/dist/components/suggestions-primitives/ItemGrid.js +42 -0
  52. package/dist/components/suggestions-primitives/ProductCard.d.ts +21 -0
  53. package/dist/components/suggestions-primitives/ProductCard.d.ts.map +1 -0
  54. package/dist/components/suggestions-primitives/ProductCard.js +46 -0
  55. package/dist/components/suggestions-primitives/ProductGrid.d.ts +17 -0
  56. package/dist/components/suggestions-primitives/ProductGrid.d.ts.map +1 -0
  57. package/dist/components/suggestions-primitives/ProductGrid.js +36 -0
  58. package/dist/components/suggestions-primitives/RecentSearchesList.d.ts +17 -0
  59. package/dist/components/suggestions-primitives/RecentSearchesList.d.ts.map +1 -0
  60. package/dist/components/suggestions-primitives/RecentSearchesList.js +39 -0
  61. package/dist/components/suggestions-primitives/SearchInput.d.ts +23 -0
  62. package/dist/components/suggestions-primitives/SearchInput.d.ts.map +1 -0
  63. package/dist/components/suggestions-primitives/SearchInput.js +95 -0
  64. package/dist/components/suggestions-primitives/SuggestionItem.d.ts +18 -0
  65. package/dist/components/suggestions-primitives/SuggestionItem.d.ts.map +1 -0
  66. package/dist/components/suggestions-primitives/SuggestionItem.js +34 -0
  67. package/dist/components/suggestions-primitives/SuggestionList.d.ts +15 -0
  68. package/dist/components/suggestions-primitives/SuggestionList.d.ts.map +1 -0
  69. package/dist/components/suggestions-primitives/SuggestionList.js +36 -0
  70. package/dist/components/suggestions-primitives/SuggestionsContext.d.ts +41 -0
  71. package/dist/components/suggestions-primitives/SuggestionsContext.d.ts.map +1 -0
  72. package/dist/components/suggestions-primitives/SuggestionsContext.js +18 -0
  73. package/dist/components/suggestions-primitives/SuggestionsDropdownComposition.d.ts +24 -0
  74. package/dist/components/suggestions-primitives/SuggestionsDropdownComposition.d.ts.map +1 -0
  75. package/dist/components/suggestions-primitives/SuggestionsDropdownComposition.js +32 -0
  76. package/dist/components/suggestions-primitives/SuggestionsError.d.ts +11 -0
  77. package/dist/components/suggestions-primitives/SuggestionsError.d.ts.map +1 -0
  78. package/dist/components/suggestions-primitives/SuggestionsError.js +19 -0
  79. package/dist/components/suggestions-primitives/SuggestionsLoading.d.ts +11 -0
  80. package/dist/components/suggestions-primitives/SuggestionsLoading.d.ts.map +1 -0
  81. package/dist/components/suggestions-primitives/SuggestionsLoading.js +17 -0
  82. package/dist/components/suggestions-primitives/SuggestionsProvider.d.ts +38 -0
  83. package/dist/components/suggestions-primitives/SuggestionsProvider.d.ts.map +1 -0
  84. package/dist/components/suggestions-primitives/SuggestionsProvider.js +222 -0
  85. package/dist/components/suggestions-primitives/TrendingList.d.ts +17 -0
  86. package/dist/components/suggestions-primitives/TrendingList.d.ts.map +1 -0
  87. package/dist/components/suggestions-primitives/TrendingList.js +41 -0
  88. package/dist/components/suggestions-primitives/index.d.ts +39 -0
  89. package/dist/components/suggestions-primitives/index.d.ts.map +1 -0
  90. package/dist/components/suggestions-primitives/index.js +24 -0
  91. package/dist/hooks/useQuerySuggestionsEnhanced.d.ts.map +1 -1
  92. package/dist/hooks/useQuerySuggestionsEnhanced.js +30 -8
  93. package/dist/hooks/useSuggestionsAnalytics.d.ts +6 -6
  94. package/dist/hooks/useSuggestionsAnalytics.js +6 -6
  95. package/dist/index.d.ts +9 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +7 -0
  98. package/dist/index.umd.js +1 -1
  99. package/dist/src/index.d.ts +630 -130
  100. package/dist/src/index.esm.js +1110 -43
  101. package/dist/src/index.esm.js.map +1 -1
  102. package/dist/src/index.js +1134 -42
  103. package/dist/src/index.js.map +1 -1
  104. package/package.json +3 -3
package/dist/src/index.js CHANGED
@@ -4536,15 +4536,16 @@ function parseHighlight(highlighted) {
4536
4536
  .replace(/__\/ais-highlight__/g, '</mark>');
4537
4537
  }
4538
4538
  function transformProduct(raw) {
4539
+ const meta = raw.metadata || {};
4539
4540
  return {
4540
4541
  id: raw.id || raw.objectID,
4541
4542
  objectID: raw.objectID || raw.id,
4542
- title: raw.title || raw.name || raw.productName,
4543
- name: raw.name || raw.title,
4544
- image: raw.image || raw.imageUrl || raw.metadata?.image,
4545
- price: raw.price || raw.sellPrice || raw.metadata?.sellPrice,
4546
- currency: raw.currency || raw.metadata?.currency || '',
4547
- url: raw.url || raw.productId || raw.metadata?.url,
4543
+ title: raw.title || raw.name || raw.productName || meta.name || meta.productName || '',
4544
+ name: raw.name || raw.title || meta.name || meta.productName || '',
4545
+ image: raw.image || raw.imageUrl || meta.image || meta.image_url || meta.images?.[0] || '',
4546
+ price: raw.price ?? raw.sellPrice ?? meta.sellPrice ?? meta.price,
4547
+ currency: raw.currency || meta.currency || '',
4548
+ url: raw.url || raw.productId || meta.url || meta.productId || '',
4548
4549
  clicks: raw.clicks,
4549
4550
  conversions: raw.conversions,
4550
4551
  revenue: raw.revenue,
@@ -4594,7 +4595,10 @@ function useQuerySuggestionsEnhanced(options) {
4594
4595
  }, [enableRecentSearches, recentSearchesKey, maxRecentSearches]);
4595
4596
  // Fetch suggestions
4596
4597
  const fetchSuggestions = React.useCallback(async (searchQuery) => {
4597
- if (!client || !searchQuery.trim()) {
4598
+ if (!client)
4599
+ return;
4600
+ // When minQueryLength is 0, allow empty query so overlay can show default/trending recommendations on open
4601
+ if (!searchQuery.trim() && minQueryLength > 0) {
4598
4602
  setSuggestions([]);
4599
4603
  setDropdownRecommendations(null);
4600
4604
  return;
@@ -4629,12 +4633,17 @@ function useQuerySuggestionsEnhanced(options) {
4629
4633
  setSuggestions(transformedSuggestions);
4630
4634
  // Extract dropdown recommendations from extensions
4631
4635
  const extensions = (response.extensions || {});
4636
+ const rawResults = response.results;
4637
+ const secondResultHits = Array.isArray(rawResults?.[1]?.hits) ? rawResults[1].hits : [];
4638
+ // Multi-index response: results[0]=suggestions, results[1]=product hits; expose results[1].hits as product_hits for fallback when extensions have no products
4639
+ const productHits = secondResultHits.length > 0 ? secondResultHits.map((h) => transformProduct(h)) : [];
4632
4640
  const recommendations = {
4633
4641
  trending_searches: Array.isArray(extensions.trending_searches) ? extensions.trending_searches : [],
4634
4642
  top_searches: Array.isArray(extensions.top_searches) ? extensions.top_searches : [],
4635
4643
  related_searches: Array.isArray(extensions.related_searches) ? extensions.related_searches : [],
4636
4644
  trending_products: Array.isArray(extensions.trending_products) ? extensions.trending_products.map(transformProduct) : [],
4637
4645
  item_recommendations: Array.isArray(extensions.item_recommendations) ? extensions.item_recommendations.map(transformProduct) : [],
4646
+ product_hits: productHits.length > 0 ? productHits : undefined,
4638
4647
  popular_brands: Array.isArray(extensions.popular_brands) ? extensions.popular_brands : [],
4639
4648
  filtered_tabs: Array.isArray(extensions.filtered_tabs) ? extensions.filtered_tabs.map(transformFilteredTab) : [],
4640
4649
  processing_time_ms: typeof extensions.processing_time_ms === 'number' ? extensions.processing_time_ms : undefined,
@@ -4694,6 +4703,7 @@ function useQuerySuggestionsEnhanced(options) {
4694
4703
  }
4695
4704
  }, [
4696
4705
  client,
4706
+ minQueryLength,
4697
4707
  maxSuggestions,
4698
4708
  includeDropdownRecommendations,
4699
4709
  includeCategories,
@@ -4770,7 +4780,19 @@ function useQuerySuggestionsEnhanced(options) {
4770
4780
  const relatedSearches = dropdownRecommendations?.related_searches || [];
4771
4781
  const popularBrands = dropdownRecommendations?.popular_brands || [];
4772
4782
  const filteredTabsResult = dropdownRecommendations?.filtered_tabs || [];
4773
- const trendingProducts = dropdownRecommendations?.trending_products || [];
4783
+ // Use trending_products, then item_recommendations, then first filtered_tab's products, then results[1].hits (product_hits) so grid shows for any API shape
4784
+ const trendingProducts = React.useMemo(() => {
4785
+ const fromTrending = dropdownRecommendations?.trending_products;
4786
+ if (fromTrending && fromTrending.length > 0)
4787
+ return fromTrending;
4788
+ const fromItemRecs = dropdownRecommendations?.item_recommendations;
4789
+ if (fromItemRecs && fromItemRecs.length > 0)
4790
+ return fromItemRecs;
4791
+ const firstTab = dropdownRecommendations?.filtered_tabs?.[0];
4792
+ if (firstTab?.products && firstTab.products.length > 0)
4793
+ return firstTab.products;
4794
+ return dropdownRecommendations?.product_hits ?? [];
4795
+ }, [dropdownRecommendations?.trending_products, dropdownRecommendations?.item_recommendations, dropdownRecommendations?.filtered_tabs, dropdownRecommendations?.product_hits]);
4774
4796
  const hasContent = React.useMemo(() => {
4775
4797
  return (suggestions.length > 0 ||
4776
4798
  recentSearches.length > 0 ||
@@ -4858,7 +4880,7 @@ function useQuerySuggestionsEnhanced(options) {
4858
4880
  // ============================================================================
4859
4881
  // Styles
4860
4882
  // ============================================================================
4861
- const defaultStyles = {
4883
+ const defaultStyles$1 = {
4862
4884
  container: {
4863
4885
  backgroundColor: 'var(--seekora-bg-surface, #ffffff)',
4864
4886
  border: '1px solid var(--seekora-border-color, #e5e7eb)',
@@ -5112,7 +5134,7 @@ const QuerySuggestionsDropdown = React.forwardRef(function QuerySuggestionsDropd
5112
5134
  return text;
5113
5135
  }
5114
5136
  const parts = text.split(new RegExp(`(${searchQuery})`, 'gi'));
5115
- return parts.map((part, i) => part.toLowerCase() === searchQuery.toLowerCase() ? (React.createElement("mark", { key: i, className: classNames.suggestionItemHighlight, style: defaultStyles.highlight }, part)) : part);
5137
+ return parts.map((part, i) => part.toLowerCase() === searchQuery.toLowerCase() ? (React.createElement("mark", { key: i, className: classNames.suggestionItemHighlight, style: defaultStyles$1.highlight }, part)) : part);
5116
5138
  }, [highlight.enabled, classNames.suggestionItemHighlight]);
5117
5139
  // Parse highlighted query from API
5118
5140
  const parseHighlightedQuery = React.useCallback((suggestion) => {
@@ -5128,30 +5150,30 @@ const QuerySuggestionsDropdown = React.forwardRef(function QuerySuggestionsDropd
5128
5150
  return renderSuggestion(suggestion, index, isActive, (text) => highlightText(text, query));
5129
5151
  }
5130
5152
  return (React.createElement("div", { style: {
5131
- ...defaultStyles.suggestionItem,
5132
- ...(isActive ? defaultStyles.suggestionItemActive : {}),
5153
+ ...defaultStyles$1.suggestionItem,
5154
+ ...(isActive ? defaultStyles$1.suggestionItemActive : {}),
5133
5155
  } },
5134
- React.createElement(SearchIcon$9, { style: defaultStyles.recentIcon }),
5135
- React.createElement("span", { style: defaultStyles.suggestionQuery }, parseHighlightedQuery(suggestion)),
5136
- showCounts && suggestion.count !== undefined && (React.createElement("span", { style: defaultStyles.suggestionCount }, suggestion.count.toLocaleString()))));
5156
+ React.createElement(SearchIcon$9, { style: defaultStyles$1.recentIcon }),
5157
+ React.createElement("span", { style: defaultStyles$1.suggestionQuery }, parseHighlightedQuery(suggestion)),
5158
+ showCounts && suggestion.count !== undefined && (React.createElement("span", { style: defaultStyles$1.suggestionCount }, suggestion.count.toLocaleString()))));
5137
5159
  };
5138
5160
  const renderRecentSearchItem = (search, index, isActive) => {
5139
5161
  if (renderRecentSearch) {
5140
5162
  return renderRecentSearch(search, index, isActive);
5141
5163
  }
5142
5164
  return (React.createElement("div", { style: {
5143
- ...defaultStyles.suggestionItem,
5144
- ...(isActive ? defaultStyles.suggestionItemActive : {}),
5165
+ ...defaultStyles$1.suggestionItem,
5166
+ ...(isActive ? defaultStyles$1.suggestionItemActive : {}),
5145
5167
  } },
5146
- React.createElement(ClockIcon$6, { style: defaultStyles.recentIcon }),
5147
- React.createElement("span", { style: defaultStyles.suggestionQuery }, search.query),
5168
+ React.createElement(ClockIcon$6, { style: defaultStyles$1.recentIcon }),
5169
+ React.createElement("span", { style: defaultStyles$1.suggestionQuery }, search.query),
5148
5170
  React.createElement("button", { type: "button", onClick: (e) => {
5149
5171
  e.stopPropagation();
5150
5172
  removeRecentSearch(search.query);
5151
5173
  onRecentSearchRemove?.(search);
5152
5174
  }, style: {
5153
- ...defaultStyles.removeButton,
5154
- ...(isActive ? defaultStyles.removeButtonVisible : {}),
5175
+ ...defaultStyles$1.removeButton,
5176
+ ...(isActive ? defaultStyles$1.removeButtonVisible : {}),
5155
5177
  }, "aria-label": `Remove ${search.query} from recent searches` },
5156
5178
  React.createElement(CloseIcon$1, null))));
5157
5179
  };
@@ -5175,24 +5197,24 @@ const QuerySuggestionsDropdown = React.forwardRef(function QuerySuggestionsDropd
5175
5197
  position,
5176
5198
  width,
5177
5199
  zIndex,
5178
- ...defaultStyles.container,
5200
+ ...defaultStyles$1.container,
5179
5201
  ...animationStyle,
5180
5202
  ...style,
5181
5203
  } },
5182
- loading && showLoading && (React.createElement("div", { className: classNames.loadingState, style: defaultStyles.loadingState }, renderLoading ? renderLoading() : (React.createElement(React.Fragment, null,
5204
+ loading && showLoading && (React.createElement("div", { className: classNames.loadingState, style: defaultStyles$1.loadingState }, renderLoading ? renderLoading() : (React.createElement(React.Fragment, null,
5183
5205
  React.createElement(LoadingSpinner, null),
5184
5206
  React.createElement("span", null, "Searching..."))))),
5185
5207
  !loading && showRecent && (React.createElement("div", { className: clsx('seekora-suggestions-section', classNames.section, classNames.recentSearches) },
5186
- React.createElement("div", { className: classNames.sectionTitle, style: defaultStyles.sectionTitle }, "Recent Searches"),
5208
+ React.createElement("div", { className: classNames.sectionTitle, style: defaultStyles$1.sectionTitle }, "Recent Searches"),
5187
5209
  recentSearches.slice(0, maxRecentSearches).map((search, index) => {
5188
5210
  const isActive = activeIndex === index;
5189
5211
  return (React.createElement("div", { key: `recent-${search.query}-${index}`, role: "option", "aria-selected": isActive, className: clsx(classNames.recentItem, isActive && classNames.suggestionItemActive), onClick: () => {
5190
5212
  onRecentSearchClick?.(search);
5191
5213
  }, onMouseEnter: () => setActiveIndex(index) }, renderRecentSearchItem(search, index, isActive)));
5192
5214
  }))),
5193
- !loading && showRecent && showSuggestions && (React.createElement("div", { style: defaultStyles.divider })),
5215
+ !loading && showRecent && showSuggestions && (React.createElement("div", { style: defaultStyles$1.divider })),
5194
5216
  !loading && showSuggestions && (React.createElement("div", { className: clsx('seekora-suggestions-section', classNames.section, classNames.suggestionsList) },
5195
- query.length > 0 && (React.createElement("div", { className: classNames.sectionTitle, style: defaultStyles.sectionTitle }, "Suggestions")),
5217
+ query.length > 0 && (React.createElement("div", { className: classNames.sectionTitle, style: defaultStyles$1.sectionTitle }, "Suggestions")),
5196
5218
  suggestions.map((suggestion, index) => {
5197
5219
  const itemIndex = showRecent ? recentSearches.length + index : index;
5198
5220
  const isActive = activeIndex === itemIndex;
@@ -5201,19 +5223,19 @@ const QuerySuggestionsDropdown = React.forwardRef(function QuerySuggestionsDropd
5201
5223
  addRecentSearch(suggestion.query, suggestion.count);
5202
5224
  }, onMouseEnter: () => setActiveIndex(itemIndex) }, renderSuggestionItem(suggestion, index, isActive)));
5203
5225
  }))),
5204
- showEmpty && (React.createElement("div", { className: classNames.emptyState, style: defaultStyles.emptyState }, renderEmpty ? renderEmpty() : (React.createElement(React.Fragment, null,
5226
+ showEmpty && (React.createElement("div", { className: classNames.emptyState, style: defaultStyles$1.emptyState }, renderEmpty ? renderEmpty() : (React.createElement(React.Fragment, null,
5205
5227
  React.createElement(SearchIcon$9, { style: { width: 24, height: 24, marginBottom: 8, opacity: 0.5 } }),
5206
5228
  React.createElement("span", null,
5207
5229
  "No suggestions found for \"",
5208
5230
  query,
5209
5231
  "\""))))),
5210
- footer || (keyboardNav.enabled && allItems.length > 0) ? (React.createElement("div", { className: classNames.footer, style: defaultStyles.footer }, footer || (React.createElement("div", { style: defaultStyles.keyboardHint },
5211
- React.createElement("span", { style: defaultStyles.keyboardKey }, "\u2191"),
5212
- React.createElement("span", { style: defaultStyles.keyboardKey }, "\u2193"),
5232
+ footer || (keyboardNav.enabled && allItems.length > 0) ? (React.createElement("div", { className: classNames.footer, style: defaultStyles$1.footer }, footer || (React.createElement("div", { style: defaultStyles$1.keyboardHint },
5233
+ React.createElement("span", { style: defaultStyles$1.keyboardKey }, "\u2191"),
5234
+ React.createElement("span", { style: defaultStyles$1.keyboardKey }, "\u2193"),
5213
5235
  React.createElement("span", null, "to navigate"),
5214
- React.createElement("span", { style: defaultStyles.keyboardKey }, "\u21B5"),
5236
+ React.createElement("span", { style: defaultStyles$1.keyboardKey }, "\u21B5"),
5215
5237
  React.createElement("span", null, "to select"),
5216
- React.createElement("span", { style: defaultStyles.keyboardKey }, "esc"),
5238
+ React.createElement("span", { style: defaultStyles$1.keyboardKey }, "esc"),
5217
5239
  React.createElement("span", null, "to close"))))) : null,
5218
5240
  React.createElement("style", null, `
5219
5241
  @keyframes spin {
@@ -6214,12 +6236,12 @@ const FederatedDropdown = React.forwardRef(function FederatedDropdown(props, ref
6214
6236
  /**
6215
6237
  * Suggestions Analytics Hook
6216
6238
  *
6217
- * Provides analytics tracking for query suggestions components:
6218
- * - Suggestion clicks/selections
6219
- * - Suggestion impressions
6220
- * - Product clicks from dropdown
6221
- * - Category/brand clicks
6222
- * - Search submissions from suggestions
6239
+ * Provides analytics tracking for query suggestions components using
6240
+ * Analytics V3 event structure and schema/fields (event_name, event_ts,
6241
+ * correlation_id/search_id via context, and V3 top-level fields such as
6242
+ * original_query, position, section, clicked_item_id, click_type, tab_id,
6243
+ * suggestions_count, etc.). All events are sent via client.trackEvent(payload, context)
6244
+ * so the SDK can attach correlation_id, search_id, event_ts, orgcode, xstoreid.
6223
6245
  */
6224
6246
  // ============================================================================
6225
6247
  // Event Names
@@ -6518,13 +6540,13 @@ const styles = {
6518
6540
  // ============================================================================
6519
6541
  const SearchIcon$6 = () => (React.createElement("svg", { viewBox: "0 0 20 20", fill: "currentColor", style: styles.searchIcon },
6520
6542
  React.createElement("path", { fillRule: "evenodd", d: "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", clipRule: "evenodd" })));
6521
- const ClearIcon = () => (React.createElement("svg", { viewBox: "0 0 20 20", fill: "currentColor", style: { width: 16, height: 16 } },
6543
+ const ClearIcon$1 = () => (React.createElement("svg", { viewBox: "0 0 20 20", fill: "currentColor", style: { width: 16, height: 16 } },
6522
6544
  React.createElement("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" })));
6523
6545
  // ============================================================================
6524
6546
  // Component
6525
6547
  // ============================================================================
6526
6548
  const SearchBarWithSuggestions = React.forwardRef(function SearchBarWithSuggestions(props, ref) {
6527
- const { variant = 'classic', placeholder = 'Search...', initialQuery = '', value, onQueryChange, onSearch, onSuggestionSelect, onProductClick, showSearchButton = false, searchButtonText = 'Search', showClearButton = true, autoFocus = false, minQueryLength = 1, maxSuggestions = 8, debounceMs = 200, showRecentSearches = true, showTrendingOnEmpty = true, includeDropdownRecommendations = false, filteredTabs, enableAnalytics = true, analyticsTags, includeFacets, includeCategories, dropdownWidth, dropdownMaxHeight, classNames = {}, style, inputStyle, ariaLabel = 'Search', } = props;
6549
+ const { variant = 'classic', placeholder = 'Search...', initialQuery = '', value, onQueryChange, onSearch, onSuggestionSelect, onProductClick, showSearchButton = false, searchButtonText = 'Search', showClearButton = true, autoFocus = false, minQueryLength = 1, maxSuggestions = 8, maxProducts = 8, debounceMs = 200, showRecentSearches = true, showTrendingOnEmpty = true, includeDropdownRecommendations = false, filteredTabs, enableAnalytics = true, analyticsTags, includeFacets, includeCategories, dropdownWidth, dropdownMaxHeight, classNames = {}, style, inputStyle, ariaLabel = 'Search', } = props;
6528
6550
  const { client } = useSearchContext();
6529
6551
  const inputRef = React.useRef(null);
6530
6552
  const dropdownRef = React.useRef(null);
@@ -6683,7 +6705,7 @@ const SearchBarWithSuggestions = React.forwardRef(function SearchBarWithSuggesti
6683
6705
  case 'rich':
6684
6706
  return (React.createElement(RichQuerySuggestions, { ref: dropdownRef, ...commonProps, includeDropdownRecommendations: includeDropdownRecommendations, includeCategories: true, width: dropdownWidth || '100%', maxHeight: dropdownMaxHeight || '480px' }));
6685
6707
  case 'federated':
6686
- return (React.createElement(FederatedDropdown, { ref: dropdownRef, ...commonProps, filteredTabs: filteredTabs, showProducts: true, showBrands: true, showFilteredTabs: !!filteredTabs, onProductClick: handleProductClick, width: dropdownWidth || '800px', maxHeight: dropdownMaxHeight || '600px', includeFacets: includeFacets, includeCategories: includeCategories, includeDropdownRecommendations: includeDropdownRecommendations }));
6708
+ return (React.createElement(FederatedDropdown, { ref: dropdownRef, ...commonProps, maxProducts: maxProducts, filteredTabs: filteredTabs, showProducts: true, showBrands: true, showFilteredTabs: !!filteredTabs, onProductClick: handleProductClick, width: dropdownWidth || '800px', maxHeight: dropdownMaxHeight || '600px', includeFacets: includeFacets, includeCategories: includeCategories, includeDropdownRecommendations: includeDropdownRecommendations }));
6687
6709
  case 'compact':
6688
6710
  return (React.createElement(QuerySuggestionsDropdown, { ref: dropdownRef, ...commonProps, maxSuggestions: 5, showCounts: false, width: dropdownWidth || '100%' }));
6689
6711
  case 'classic':
@@ -6700,11 +6722,1056 @@ const SearchBarWithSuggestions = React.forwardRef(function SearchBarWithSuggesti
6700
6722
  React.createElement(SearchIcon$6, null),
6701
6723
  React.createElement("input", { ref: inputRef, type: "text", value: query, onChange: handleQueryChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, placeholder: placeholder, autoFocus: autoFocus, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: false, "aria-label": ariaLabel, "aria-expanded": isDropdownOpen, "aria-haspopup": "listbox", "aria-autocomplete": "list", role: "combobox", className: classNames.input, style: { ...styles.input, ...inputStyle } }),
6702
6724
  showClearButton && query && (React.createElement("button", { type: "button", onClick: handleClear, className: classNames.clearButton, style: styles.clearButton, "aria-label": "Clear search" },
6703
- React.createElement(ClearIcon, null))),
6725
+ React.createElement(ClearIcon$1, null))),
6704
6726
  showSearchButton && (React.createElement("button", { type: "submit", className: classNames.button, style: styles.searchButton }, searchButtonText)))),
6705
6727
  React.createElement("div", { className: classNames.dropdown }, renderDropdown())));
6706
6728
  });
6707
6729
 
6730
+ /**
6731
+ * Suggestions Context
6732
+ *
6733
+ * Type definitions and React context for composable suggestions primitives.
6734
+ * Consuming components use useSuggestionsContext() for state and actions.
6735
+ */
6736
+ const SuggestionsContext = React.createContext(null);
6737
+ function useSuggestionsContext() {
6738
+ const context = React.useContext(SuggestionsContext);
6739
+ if (!context) {
6740
+ const error = new Error('useSuggestionsContext must be used within a SuggestionsProvider');
6741
+ log.error('SuggestionsContext: not available', { error: error.message });
6742
+ throw error;
6743
+ }
6744
+ return context;
6745
+ }
6746
+
6747
+ /**
6748
+ * SuggestionsProvider
6749
+ *
6750
+ * Provides suggestions state (query, suggestions, products, recent, trending, tabs),
6751
+ * actions (selectSuggestion, selectProduct, submitSearch, etc.), and keyboard navigation.
6752
+ * Uses useQuerySuggestionsEnhanced and useSuggestionsAnalytics; must be inside SearchProvider.
6753
+ */
6754
+ function SuggestionsProvider({ children, minQueryLength = 1, debounceMs = 200, maxSuggestions = 10, includeDropdownRecommendations = true, filteredTabs, enableAnalytics = true, analyticsTags, onSearch, onSuggestionSelect, onProductClick, }) {
6755
+ const { client } = useSearchContext();
6756
+ const [query, setQueryState] = React.useState('');
6757
+ const [isOpen, setIsOpenState] = React.useState(false);
6758
+ const [activeIndex, setActiveIndex] = React.useState(-1);
6759
+ const [activeTabId, setActiveTabId] = React.useState('');
6760
+ const suggestionsData = useQuerySuggestionsEnhanced({
6761
+ client,
6762
+ query,
6763
+ enabled: isOpen,
6764
+ minQueryLength,
6765
+ debounceMs,
6766
+ maxSuggestions,
6767
+ includeDropdownRecommendations,
6768
+ filteredTabs,
6769
+ includeCategories: true,
6770
+ enableRecentSearches: true,
6771
+ maxRecentSearches: 8,
6772
+ analyticsTags,
6773
+ });
6774
+ const analytics = useSuggestionsAnalytics({
6775
+ client,
6776
+ enabled: enableAnalytics,
6777
+ analyticsTags,
6778
+ trackClicks: true,
6779
+ trackImpressions: true,
6780
+ });
6781
+ const { suggestions, recentSearches, trendingSearches, trendingProducts, filteredTabs: filteredTabsData, loading, error, hasContent, getAllNavigableItems, addRecentSearch, } = suggestionsData;
6782
+ const onSearchRef = React.useRef(onSearch);
6783
+ const onSuggestionSelectRef = React.useRef(onSuggestionSelect);
6784
+ const onProductClickRef = React.useRef(onProductClick);
6785
+ onSearchRef.current = onSearch;
6786
+ onSuggestionSelectRef.current = onSuggestionSelect;
6787
+ onProductClickRef.current = onProductClick;
6788
+ const setQuery = React.useCallback((q) => {
6789
+ setQueryState(q);
6790
+ setActiveIndex(-1);
6791
+ }, []);
6792
+ const close = React.useCallback(() => {
6793
+ analytics.trackDropdownClose(query);
6794
+ setIsOpenState(false);
6795
+ setActiveIndex(-1);
6796
+ }, [analytics, query]);
6797
+ const setIsOpen = React.useCallback((open) => {
6798
+ if (open) {
6799
+ setIsOpenState(true);
6800
+ analytics.trackDropdownOpen(query);
6801
+ }
6802
+ else {
6803
+ close();
6804
+ }
6805
+ }, [analytics, query, close]);
6806
+ const selectSuggestion = React.useCallback((suggestion, position) => {
6807
+ analytics.trackSuggestionClick({
6808
+ suggestion,
6809
+ position,
6810
+ query,
6811
+ totalSuggestions: suggestions.length,
6812
+ });
6813
+ setQuery(suggestion.query);
6814
+ onSuggestionSelectRef.current?.(suggestion);
6815
+ analytics.trackSearchSubmit(suggestion.query, true, suggestion);
6816
+ addRecentSearch(suggestion.query);
6817
+ close();
6818
+ onSearchRef.current?.(suggestion.query);
6819
+ }, [analytics, query, suggestions.length, addRecentSearch, close]);
6820
+ const selectProduct = React.useCallback((product, position, section, tabId) => {
6821
+ analytics.trackProductClick({
6822
+ product,
6823
+ position,
6824
+ section: section ?? 'products',
6825
+ tabId,
6826
+ query,
6827
+ });
6828
+ close();
6829
+ onProductClickRef.current?.(product);
6830
+ }, [analytics, query, close]);
6831
+ const selectRecentSearch = React.useCallback((search) => {
6832
+ analytics.trackRecentSearchClick(search);
6833
+ setQuery(search.query);
6834
+ close();
6835
+ onSearchRef.current?.(search.query);
6836
+ analytics.trackSearchSubmit(search.query, false);
6837
+ }, [analytics, close]);
6838
+ const selectTrendingSearch = React.useCallback((trending, position) => {
6839
+ analytics.trackTrendingClick(trending, position);
6840
+ setQuery(trending.query);
6841
+ close();
6842
+ onSearchRef.current?.(trending.query);
6843
+ analytics.trackSearchSubmit(trending.query, false);
6844
+ }, [analytics, close]);
6845
+ const setActiveTab = React.useCallback((tab) => {
6846
+ setActiveTabId(tab.id);
6847
+ analytics.trackTabSelect(tab, query);
6848
+ }, [analytics, query]);
6849
+ const submitSearch = React.useCallback((q, fromSuggestion, suggestion) => {
6850
+ const trimmed = q.trim();
6851
+ if (!trimmed)
6852
+ return;
6853
+ analytics.trackSearchSubmit(trimmed, !!fromSuggestion, suggestion);
6854
+ addRecentSearch(trimmed);
6855
+ close();
6856
+ onSearchRef.current?.(trimmed);
6857
+ }, [analytics, addRecentSearch, close]);
6858
+ const navigateNext = React.useCallback(() => {
6859
+ const items = getAllNavigableItems();
6860
+ if (items.length === 0)
6861
+ return;
6862
+ setActiveIndex((i) => (i < items.length - 1 ? i + 1 : 0));
6863
+ }, [getAllNavigableItems]);
6864
+ const navigatePrev = React.useCallback(() => {
6865
+ const items = getAllNavigableItems();
6866
+ if (items.length === 0)
6867
+ return;
6868
+ setActiveIndex((i) => (i <= 0 ? items.length - 1 : i - 1));
6869
+ }, [getAllNavigableItems]);
6870
+ const selectActive = React.useCallback(() => {
6871
+ const items = getAllNavigableItems();
6872
+ if (activeIndex < 0 || activeIndex >= items.length) {
6873
+ submitSearch(query);
6874
+ return;
6875
+ }
6876
+ const item = items[activeIndex];
6877
+ switch (item.type) {
6878
+ case 'suggestion':
6879
+ selectSuggestion(item.data, item.index);
6880
+ break;
6881
+ case 'product':
6882
+ selectProduct(item.data, item.index, 'products');
6883
+ break;
6884
+ case 'recent':
6885
+ selectRecentSearch(item.data);
6886
+ break;
6887
+ case 'trending':
6888
+ selectTrendingSearch(item.data, item.index);
6889
+ break;
6890
+ default:
6891
+ submitSearch(query);
6892
+ }
6893
+ }, [activeIndex, getAllNavigableItems, query, submitSearch, selectSuggestion, selectProduct, selectRecentSearch, selectTrendingSearch]);
6894
+ // Impression when open and content changes
6895
+ React.useEffect(() => {
6896
+ if (!isOpen || !hasContent || !query)
6897
+ return;
6898
+ analytics.trackImpression({
6899
+ suggestions,
6900
+ products: trendingProducts,
6901
+ query,
6902
+ timestamp: Date.now(),
6903
+ });
6904
+ }, [isOpen, hasContent, query, suggestions, trendingProducts, analytics]);
6905
+ const value = React.useMemo(() => ({
6906
+ query,
6907
+ setQuery,
6908
+ isOpen,
6909
+ setIsOpen,
6910
+ activeIndex,
6911
+ setActiveIndex,
6912
+ suggestions,
6913
+ recentSearches,
6914
+ trendingSearches,
6915
+ trendingProducts,
6916
+ filteredTabs: filteredTabsData,
6917
+ activeTabId,
6918
+ setActiveTabId,
6919
+ loading,
6920
+ error,
6921
+ hasContent,
6922
+ getAllNavigableItems,
6923
+ selectSuggestion,
6924
+ selectProduct,
6925
+ selectRecentSearch,
6926
+ selectTrendingSearch,
6927
+ setActiveTab,
6928
+ submitSearch,
6929
+ close,
6930
+ navigateNext,
6931
+ navigatePrev,
6932
+ selectActive,
6933
+ }), [
6934
+ query,
6935
+ setQuery,
6936
+ isOpen,
6937
+ setIsOpen,
6938
+ activeIndex,
6939
+ setActiveIndex,
6940
+ suggestions,
6941
+ recentSearches,
6942
+ trendingSearches,
6943
+ trendingProducts,
6944
+ filteredTabsData,
6945
+ activeTabId,
6946
+ setActiveTabId,
6947
+ loading,
6948
+ error,
6949
+ hasContent,
6950
+ getAllNavigableItems,
6951
+ selectSuggestion,
6952
+ selectProduct,
6953
+ selectRecentSearch,
6954
+ selectTrendingSearch,
6955
+ setActiveTab,
6956
+ submitSearch,
6957
+ close,
6958
+ navigateNext,
6959
+ navigatePrev,
6960
+ selectActive,
6961
+ ]);
6962
+ return (React.createElement(SuggestionsContext.Provider, { value: value }, children));
6963
+ }
6964
+
6965
+ /**
6966
+ * SearchInput – suggestion-scoped search input (primitive)
6967
+ *
6968
+ * Single input bound to suggestions context: query, setQuery, focus opens dropdown,
6969
+ * keydown ArrowUp/Down/Enter/Escape delegates to context. Overridable via className/style.
6970
+ */
6971
+ const defaultStyles = {
6972
+ position: 'relative',
6973
+ width: '100%',
6974
+ };
6975
+ const inputWrapperStyles = {
6976
+ display: 'flex',
6977
+ alignItems: 'center',
6978
+ gap: '8px',
6979
+ padding: '8px 12px',
6980
+ border: '1px solid var(--seekora-border-color, #e5e7eb)',
6981
+ borderRadius: 'var(--seekora-border-radius, 6px)',
6982
+ backgroundColor: 'var(--seekora-bg-surface, #fff)',
6983
+ transition: 'border-color 150ms ease, box-shadow 150ms ease',
6984
+ };
6985
+ const inputStyles = {
6986
+ flex: 1,
6987
+ minWidth: 0,
6988
+ padding: '8px 0',
6989
+ border: 'none',
6990
+ outline: 'none',
6991
+ backgroundColor: 'transparent',
6992
+ fontSize: 'inherit',
6993
+ color: 'var(--seekora-text-primary, #111827)',
6994
+ fontFamily: 'inherit',
6995
+ };
6996
+ function SearchInput({ placeholder = 'Search...', autoFocus = false, showClearButton = true, closeOnBlur = true, leftIcon, className, style, inputClassName, inputStyle, ariaLabel = 'Search', }) {
6997
+ const { query, setQuery, isOpen, setIsOpen, navigateNext, navigatePrev, selectActive, close, } = useSuggestionsContext();
6998
+ const inputRef = React.useRef(null);
6999
+ const handleFocus = React.useCallback(() => {
7000
+ setIsOpen(true);
7001
+ }, [setIsOpen]);
7002
+ const handleBlur = React.useCallback(() => {
7003
+ if (!closeOnBlur)
7004
+ return;
7005
+ setTimeout(() => close(), 200);
7006
+ }, [close, closeOnBlur]);
7007
+ const handleChange = React.useCallback((e) => {
7008
+ setQuery(e.target.value);
7009
+ }, [setQuery]);
7010
+ const handleKeyDown = React.useCallback((e) => {
7011
+ if (e.key === 'ArrowDown') {
7012
+ e.preventDefault();
7013
+ navigateNext();
7014
+ return;
7015
+ }
7016
+ if (e.key === 'ArrowUp') {
7017
+ e.preventDefault();
7018
+ navigatePrev();
7019
+ return;
7020
+ }
7021
+ if (e.key === 'Enter') {
7022
+ e.preventDefault();
7023
+ selectActive();
7024
+ return;
7025
+ }
7026
+ if (e.key === 'Escape') {
7027
+ e.preventDefault();
7028
+ close();
7029
+ inputRef.current?.blur();
7030
+ return;
7031
+ }
7032
+ }, [navigateNext, navigatePrev, selectActive, close]);
7033
+ const handleClear = React.useCallback(() => {
7034
+ setQuery('');
7035
+ inputRef.current?.focus();
7036
+ }, [setQuery]);
7037
+ return (React.createElement("div", { className: clsx('seekora-suggestions-search-input-wrapper', className), style: { ...defaultStyles, ...style } },
7038
+ React.createElement("div", { className: "seekora-suggestions-input-wrapper", style: inputWrapperStyles },
7039
+ leftIcon ? (React.createElement("span", { className: "seekora-suggestions-input-left-icon", style: { display: 'flex', flexShrink: 0, color: 'var(--seekora-text-secondary, #6b7280)' } }, leftIcon)) : null,
7040
+ React.createElement("input", { ref: inputRef, type: "text", value: query, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, placeholder: placeholder, autoFocus: autoFocus, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: false, "aria-label": ariaLabel, "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-autocomplete": "list", role: "combobox", className: clsx('seekora-suggestions-input', inputClassName), style: { ...inputStyles, ...inputStyle } }),
7041
+ showClearButton && query ? (React.createElement("button", { type: "button", onClick: handleClear, className: "seekora-suggestions-input-clear", "aria-label": "Clear search", style: {
7042
+ padding: 4,
7043
+ border: 'none',
7044
+ background: 'transparent',
7045
+ cursor: 'pointer',
7046
+ color: 'var(--seekora-text-secondary, #6b7280)',
7047
+ display: 'flex',
7048
+ alignItems: 'center',
7049
+ justifyContent: 'center',
7050
+ } },
7051
+ React.createElement(ClearIcon, null))) : null)));
7052
+ }
7053
+ function ClearIcon() {
7054
+ return (React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": true },
7055
+ React.createElement("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" })));
7056
+ }
7057
+
7058
+ /**
7059
+ * DropdownPanel – floating panel for suggestions (primitive)
7060
+ *
7061
+ * Shows when isOpen; handles click-outside and Escape to close. Children = any composition
7062
+ * of SuggestionList, ProductGrid, etc. Position and z-index configurable.
7063
+ */
7064
+ function DropdownPanel({ children, position = 'absolute', top = '100%', left = 0, right, width = '100%', maxHeight = '80vh', zIndex = 1000, className, style, closeOnClickOutside = true, closeOnEscape = true, }) {
7065
+ const { isOpen, close } = useSuggestionsContext();
7066
+ const panelRef = React.useRef(null);
7067
+ React.useEffect(() => {
7068
+ if (!closeOnEscape || !isOpen)
7069
+ return;
7070
+ const handleKeyDown = (e) => {
7071
+ if (e.key === 'Escape')
7072
+ close();
7073
+ };
7074
+ window.addEventListener('keydown', handleKeyDown);
7075
+ return () => window.removeEventListener('keydown', handleKeyDown);
7076
+ }, [closeOnEscape, isOpen, close]);
7077
+ const handleClickOutside = React.useCallback((e) => {
7078
+ if (!closeOnClickOutside || !panelRef.current)
7079
+ return;
7080
+ const target = e.target;
7081
+ if (!panelRef.current.contains(target))
7082
+ close();
7083
+ }, [closeOnClickOutside, close]);
7084
+ React.useEffect(() => {
7085
+ if (!isOpen)
7086
+ return;
7087
+ document.addEventListener('mousedown', handleClickOutside);
7088
+ return () => document.removeEventListener('mousedown', handleClickOutside);
7089
+ }, [isOpen, handleClickOutside]);
7090
+ if (!isOpen)
7091
+ return null;
7092
+ const panelStyle = {
7093
+ position,
7094
+ top,
7095
+ left,
7096
+ right,
7097
+ width,
7098
+ maxHeight,
7099
+ zIndex,
7100
+ overflow: 'auto',
7101
+ backgroundColor: 'var(--seekora-bg-surface, #fff)',
7102
+ border: '1px solid var(--seekora-border-color, #e5e7eb)',
7103
+ borderRadius: 'var(--seekora-border-radius, 6px)',
7104
+ boxShadow: 'var(--seekora-shadow-lg, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05))',
7105
+ marginTop: 4,
7106
+ };
7107
+ return (React.createElement("div", { ref: panelRef, role: "listbox", className: clsx('seekora-suggestions-dropdown-panel', className), style: { ...panelStyle, ...style } }, children));
7108
+ }
7109
+
7110
+ /**
7111
+ * SuggestionItem – single text suggestion row (primitive)
7112
+ *
7113
+ * Highlights when isActive; onClick calls context selectSuggestion. Overridable via className/style.
7114
+ */
7115
+ const defaultItemStyle = {
7116
+ padding: '10px 12px',
7117
+ cursor: 'pointer',
7118
+ listStyle: 'none',
7119
+ border: 'none',
7120
+ width: '100%',
7121
+ textAlign: 'left',
7122
+ fontSize: 'inherit',
7123
+ fontFamily: 'inherit',
7124
+ backgroundColor: 'transparent',
7125
+ color: 'var(--seekora-text-primary, #111827)',
7126
+ transition: 'background-color 120ms ease',
7127
+ };
7128
+ function SuggestionItem({ suggestion, index, isActive, onSelect, className, style, renderHighlight, }) {
7129
+ const displayText = suggestion.highlightedQuery ?? suggestion.query;
7130
+ const content = renderHighlight ? renderHighlight(displayText) : displayText;
7131
+ return (React.createElement("li", { role: "option", "aria-selected": isActive, id: `seekora-suggestion-${index}`, className: clsx('seekora-suggestions-item', isActive && 'seekora-suggestions-item--active', className), style: {
7132
+ ...defaultItemStyle,
7133
+ ...(isActive ? { backgroundColor: 'var(--seekora-bg-hover, #f3f4f6)' } : {}),
7134
+ ...style,
7135
+ }, onMouseDown: (e) => {
7136
+ e.preventDefault();
7137
+ onSelect();
7138
+ } },
7139
+ content,
7140
+ suggestion.count != null ? (React.createElement("span", { className: "seekora-suggestions-item-count", style: { marginLeft: 8, color: 'var(--seekora-text-secondary, #6b7280)', fontSize: '0.875em' } }, suggestion.count)) : null));
7141
+ }
7142
+
7143
+ /**
7144
+ * SuggestionsLoading – loading indicator (primitive)
7145
+ */
7146
+ function SuggestionsLoading({ className, style, text = 'Loading...' }) {
7147
+ const { loading } = useSuggestionsContext();
7148
+ if (!loading)
7149
+ return null;
7150
+ return (React.createElement("div", { className: clsx('seekora-suggestions-loading', className), style: {
7151
+ padding: 16,
7152
+ color: 'var(--seekora-text-secondary, #6b7280)',
7153
+ fontSize: '0.875rem',
7154
+ ...style,
7155
+ } }, text));
7156
+ }
7157
+
7158
+ /**
7159
+ * SuggestionList – container for text suggestions (primitive)
7160
+ *
7161
+ * Renders list of SuggestionItem from context; uses activeIndex for highlight. Optional renderItem.
7162
+ */
7163
+ const listStyle = {
7164
+ margin: 0,
7165
+ padding: '4px 0',
7166
+ };
7167
+ function SuggestionList({ maxItems = 10, className, style, listClassName, renderItem, }) {
7168
+ const { suggestions, activeIndex, loading, selectSuggestion, getAllNavigableItems, } = useSuggestionsContext();
7169
+ const items = suggestions.slice(0, maxItems);
7170
+ const navigableItems = getAllNavigableItems();
7171
+ const suggestionStartIndex = navigableItems.findIndex((n) => n.type === 'suggestion');
7172
+ const activeIsInSuggestions = suggestionStartIndex >= 0 && activeIndex >= suggestionStartIndex && activeIndex < suggestionStartIndex + items.length;
7173
+ if (loading) {
7174
+ return React.createElement(SuggestionsLoading, { className: className, style: style });
7175
+ }
7176
+ if (items.length === 0)
7177
+ return null;
7178
+ return (React.createElement("div", { className: clsx('seekora-suggestions-list', className), style: style },
7179
+ React.createElement("ul", { className: clsx('seekora-suggestions-list-ul', listClassName), style: listStyle, role: "listbox" }, items.map((suggestion, i) => {
7180
+ const globalIndex = suggestionStartIndex >= 0 ? suggestionStartIndex + i : i;
7181
+ const isActive = activeIsInSuggestions && activeIndex === globalIndex;
7182
+ const onSelect = () => selectSuggestion(suggestion, globalIndex);
7183
+ if (renderItem) {
7184
+ return (React.createElement("li", { key: suggestion.objectID ?? suggestion.query ?? i, role: "option" }, renderItem(suggestion, globalIndex, isActive, onSelect)));
7185
+ }
7186
+ return (React.createElement(SuggestionItem, { key: suggestion.objectID ?? suggestion.query ?? i, suggestion: suggestion, index: globalIndex, isActive: isActive, onSelect: onSelect }));
7187
+ }))));
7188
+ }
7189
+
7190
+ /**
7191
+ * ImageDisplay – configurable multi-image display (primitive)
7192
+ *
7193
+ * Variants: single, carousel, hover, thumbStrip, thumbList. Use in ItemCard,
7194
+ * ProductCard, ProductGallery. Overridable via className/style.
7195
+ */
7196
+ const imgBaseStyle = {
7197
+ width: '100%',
7198
+ aspectRatio: '1',
7199
+ objectFit: 'cover',
7200
+ borderRadius: 4,
7201
+ backgroundColor: 'var(--seekora-bg-secondary, #f3f4f6)',
7202
+ };
7203
+ function ImageDisplay({ images, variant = 'single', alt = '', className, style, carouselAutoplay = false, carouselIntervalMs = 4000, }) {
7204
+ const [index, setIndex] = React.useState(0);
7205
+ const [hovering, setHovering] = React.useState(false);
7206
+ const safeImages = Array.isArray(images) ? images.filter(Boolean) : [];
7207
+ const current = safeImages[index] ?? safeImages[0];
7208
+ if (safeImages.length === 0) {
7209
+ return React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-placeholder', className), style: { ...imgBaseStyle, ...style }, "aria-hidden": true });
7210
+ }
7211
+ if (variant === 'single') {
7212
+ return (React.createElement("img", { src: safeImages[0], alt: alt, className: clsx('seekora-img-display', 'seekora-img-single', className), style: { ...imgBaseStyle, ...style }, loading: "lazy" }));
7213
+ }
7214
+ if (variant === 'hover') {
7215
+ const showSecond = safeImages.length > 1 && hovering;
7216
+ const src = showSecond ? safeImages[1] : safeImages[0];
7217
+ return (React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-hover', className), style: { position: 'relative', ...style }, onMouseEnter: () => setHovering(true), onMouseLeave: () => setHovering(false) },
7218
+ React.createElement("img", { src: src, alt: alt, className: "seekora-img-hover-img", style: imgBaseStyle, loading: "lazy" })));
7219
+ }
7220
+ if (variant === 'carousel') {
7221
+ const go = (delta) => {
7222
+ setIndex((i) => {
7223
+ const next = i + delta;
7224
+ if (next < 0)
7225
+ return safeImages.length - 1;
7226
+ if (next >= safeImages.length)
7227
+ return 0;
7228
+ return next;
7229
+ });
7230
+ };
7231
+ return (React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-carousel', className), style: { position: 'relative', ...style } },
7232
+ React.createElement("img", { src: current, alt: alt, className: "seekora-img-carousel-main", style: imgBaseStyle, loading: "lazy" }),
7233
+ safeImages.length > 1 && (React.createElement(React.Fragment, null,
7234
+ React.createElement("button", { type: "button", "aria-label": "Previous", className: "seekora-img-carousel-prev", style: arrowStyle(true), onMouseDown: () => go(-1) }),
7235
+ React.createElement("button", { type: "button", "aria-label": "Next", className: "seekora-img-carousel-next", style: arrowStyle(false), onMouseDown: () => go(1) })))));
7236
+ }
7237
+ if (variant === 'thumbStrip' || variant === 'thumbList') {
7238
+ return (React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-thumbstrip', className), style: { display: 'flex', flexDirection: 'column', gap: 8, ...style } },
7239
+ React.createElement("img", { src: current, alt: alt, className: "seekora-img-thumb-main", style: imgBaseStyle, loading: "lazy" }),
7240
+ React.createElement("div", { className: "seekora-img-thumbs", style: { display: 'flex', gap: 4, overflowX: 'auto', paddingBottom: 4 } }, safeImages.map((src, i) => (React.createElement("button", { type: "button", key: i, className: clsx('seekora-img-thumb', i === index && 'seekora-img-thumb--active'), style: { flexShrink: 0, width: 48, height: 48, padding: 0, border: i === index ? '2px solid var(--seekora-primary)' : '1px solid transparent', borderRadius: 4, overflow: 'hidden', cursor: 'pointer', background: 'none' }, onMouseDown: () => setIndex(i) },
7241
+ React.createElement("img", { src: src, alt: "", style: { width: '100%', height: '100%', objectFit: 'cover' } })))))));
7242
+ }
7243
+ return React.createElement("img", { src: current, alt: alt, className: clsx('seekora-img-display', className), style: { ...imgBaseStyle, ...style }, loading: "lazy" });
7244
+ }
7245
+ function arrowStyle(left) {
7246
+ return {
7247
+ position: 'absolute',
7248
+ top: '50%',
7249
+ [left ? 'left' : 'right']: 8,
7250
+ transform: 'translateY(-50%)',
7251
+ width: 32,
7252
+ height: 32,
7253
+ borderRadius: '50%',
7254
+ border: '1px solid var(--seekora-border-color)',
7255
+ backgroundColor: 'var(--seekora-bg-surface)',
7256
+ cursor: 'pointer',
7257
+ display: 'flex',
7258
+ alignItems: 'center',
7259
+ justifyContent: 'center',
7260
+ };
7261
+ }
7262
+
7263
+ /**
7264
+ * ItemCard – generic item tile (primitive)
7265
+ *
7266
+ * Domain-agnostic: works for docs, articles, products, or any hit with id, title,
7267
+ * optional description, image, url. Overridable via className/style. Use for
7268
+ * search results, section blocks, or any list; for e-commerce products use ProductCard.
7269
+ */
7270
+ const cardStyle$1 = {
7271
+ display: 'flex',
7272
+ flexDirection: 'column',
7273
+ gap: 8,
7274
+ padding: 8,
7275
+ cursor: 'pointer',
7276
+ border: 'none',
7277
+ borderRadius: 'var(--seekora-border-radius, 6px)',
7278
+ backgroundColor: 'transparent',
7279
+ textAlign: 'left',
7280
+ fontSize: 'inherit',
7281
+ fontFamily: 'inherit',
7282
+ transition: 'background-color 120ms ease',
7283
+ };
7284
+ const imgStyle$1 = {
7285
+ width: '100%',
7286
+ aspectRatio: '1',
7287
+ objectFit: 'cover',
7288
+ borderRadius: 4,
7289
+ backgroundColor: 'var(--seekora-bg-secondary, #f3f4f6)',
7290
+ };
7291
+ function ItemCard({ item, position, onSelect, className, style, asLink = true, imageVariant = 'single', }) {
7292
+ const images = item.images?.length ? item.images : item.image ?? item.imageUrl ? [String(item.image ?? item.imageUrl)] : [];
7293
+ const title = item.title ?? item.primaryText ?? '';
7294
+ const description = item.description ?? item.secondaryText;
7295
+ const href = item.url;
7296
+ const content = (React.createElement(React.Fragment, null,
7297
+ images.length > 0 ? (React.createElement(ImageDisplay, { images: images, variant: imageVariant, alt: String(title), className: "seekora-item-card-image" })) : (React.createElement("div", { className: "seekora-item-card-placeholder", style: imgStyle$1, "aria-hidden": true })),
7298
+ React.createElement("span", { className: "seekora-item-card-title", style: { fontSize: '0.875rem', fontWeight: 500 } }, String(title)),
7299
+ description ? (React.createElement("span", { className: "seekora-item-card-description", style: { fontSize: '0.8125rem', color: 'var(--seekora-text-secondary, #6b7280)', lineHeight: 1.3 } }, String(description))) : null));
7300
+ const commonProps = {
7301
+ className: clsx('seekora-item-card', className),
7302
+ style: { ...cardStyle$1, ...style },
7303
+ 'data-position': position,
7304
+ onClick: onSelect,
7305
+ onMouseDown: onSelect ? (e) => { e.preventDefault(); onSelect(); } : undefined,
7306
+ };
7307
+ if (asLink && href) {
7308
+ return (React.createElement("a", { href: href, ...commonProps, "data-item-id": item.id }, content));
7309
+ }
7310
+ return (React.createElement("button", { type: "button", ...commonProps, "data-item-id": item.id }, content));
7311
+ }
7312
+
7313
+ /**
7314
+ * ItemGrid – generic grid of items (primitive)
7315
+ *
7316
+ * Domain-agnostic: accepts any items[] and onItemClick. Renders ItemCard by default
7317
+ * or custom renderItem. Use for search hits, section blocks, docs, articles; for
7318
+ * product-only lists use ProductGrid.
7319
+ */
7320
+ const defaultGridStyle = {
7321
+ display: 'grid',
7322
+ gridTemplateColumns: 'repeat(var(--seekora-grid-cols, 4), minmax(0, 1fr))',
7323
+ gap: 12,
7324
+ padding: 12,
7325
+ };
7326
+ function toGenericItem(item, getItemId, getItemTitle, getItemImage, getItemDescription, getItemUrl) {
7327
+ return {
7328
+ id: getItemId(item),
7329
+ title: getItemTitle(item),
7330
+ image: getItemImage?.(item),
7331
+ description: getItemDescription?.(item),
7332
+ url: getItemUrl?.(item),
7333
+ ...(typeof item === 'object' && item !== null ? item : {}),
7334
+ };
7335
+ }
7336
+ function ItemGrid({ items, onItemClick, getItemId = (i) => i.id, getItemTitle = (i) => i.title, getItemImage = (i) => i.image, getItemDescription = (i) => i.description, getItemUrl = (i) => i.url, renderItem, maxItems = 24, columns = 4, className, style, gridClassName, }) {
7337
+ const slice = items.slice(0, maxItems);
7338
+ if (slice.length === 0)
7339
+ return null;
7340
+ const gridStyle = {
7341
+ ...defaultGridStyle,
7342
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
7343
+ };
7344
+ return (React.createElement("div", { className: clsx('seekora-item-grid', className), style: style },
7345
+ React.createElement("div", { className: clsx('seekora-item-grid-inner', gridClassName), style: gridStyle }, slice.map((item, i) => {
7346
+ if (renderItem)
7347
+ return React.createElement(React.Fragment, { key: getItemId(item) }, renderItem(item, i));
7348
+ const generic = toGenericItem(item, getItemId, getItemTitle, getItemImage, getItemDescription, getItemUrl);
7349
+ return (React.createElement(ItemCard, { key: generic.id, item: generic, position: i, onSelect: onItemClick ? () => onItemClick(item, i) : undefined }));
7350
+ }))));
7351
+ }
7352
+
7353
+ /**
7354
+ * ProductCard – one product tile (primitive)
7355
+ *
7356
+ * Minimal layout: image (via ImageDisplay when imageVariant set), title, price.
7357
+ * onClick calls context selectProduct. Overridable via className/style.
7358
+ */
7359
+ const cardStyle = {
7360
+ display: 'flex',
7361
+ flexDirection: 'column',
7362
+ gap: 8,
7363
+ padding: 8,
7364
+ cursor: 'pointer',
7365
+ border: 'none',
7366
+ borderRadius: 'var(--seekora-border-radius, 6px)',
7367
+ backgroundColor: 'transparent',
7368
+ textAlign: 'left',
7369
+ transition: 'background-color 120ms ease',
7370
+ };
7371
+ const imgStyle = {
7372
+ width: '100%',
7373
+ aspectRatio: '1',
7374
+ objectFit: 'cover',
7375
+ borderRadius: 4,
7376
+ backgroundColor: 'var(--seekora-bg-secondary, #f3f4f6)',
7377
+ };
7378
+ function ProductCard({ product, position, section, tabId, onSelect, className, style, imageVariant = 'single', }) {
7379
+ const images = product.images?.length
7380
+ ? product.images
7381
+ : product.image ?? product.imageUrl
7382
+ ? [String(product.image ?? product.imageUrl)]
7383
+ : [];
7384
+ const title = product.title ?? product.name ?? '';
7385
+ const price = product.price != null ? (typeof product.price === 'number' ? product.price : Number(product.price)) : null;
7386
+ return (React.createElement("button", { type: "button", className: clsx('seekora-suggestions-product-card', className), style: { ...cardStyle, ...style }, onMouseDown: (e) => {
7387
+ e.preventDefault();
7388
+ onSelect();
7389
+ }, "data-position": position, "data-section": section, "data-tab-id": tabId },
7390
+ images.length > 0 ? (React.createElement(ImageDisplay, { images: images, variant: imageVariant, alt: title, className: "seekora-suggestions-product-card-image" })) : (React.createElement("div", { className: "seekora-suggestions-product-card-placeholder", style: imgStyle, "aria-hidden": true })),
7391
+ React.createElement("span", { className: "seekora-suggestions-product-card-title", style: { fontSize: '0.875rem', fontWeight: 500 } }, title),
7392
+ price != null && !Number.isNaN(price) ? (React.createElement("span", { className: "seekora-suggestions-product-card-price", style: { fontSize: '0.875rem', color: 'var(--seekora-text-secondary, #6b7280)' } },
7393
+ product.currency ?? '$',
7394
+ price.toFixed(2))) : null));
7395
+ }
7396
+
7397
+ /**
7398
+ * ProductGrid – grid of product cards from context (primitive)
7399
+ *
7400
+ * Uses trendingProducts or active tab products; each click calls context selectProduct.
7401
+ */
7402
+ function ProductGrid({ maxItems = 8, source = 'trending', columns = 4, className, style, gridClassName, }) {
7403
+ const { trendingProducts, filteredTabs, activeTabId, selectProduct, getAllNavigableItems, } = useSuggestionsContext();
7404
+ const products = React.useMemo(() => {
7405
+ if (source === 'trending')
7406
+ return trendingProducts;
7407
+ const tab = filteredTabs.find((t) => t.id === (source === 'tab' ? activeTabId : source));
7408
+ return tab?.products ?? [];
7409
+ }, [source, activeTabId, trendingProducts, filteredTabs]);
7410
+ const items = products.slice(0, maxItems);
7411
+ const navigableItems = getAllNavigableItems();
7412
+ const productStartIndex = navigableItems.findIndex((n) => n.type === 'product');
7413
+ if (items.length === 0)
7414
+ return null;
7415
+ const gridStyle = {
7416
+ display: 'grid',
7417
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
7418
+ gap: 12,
7419
+ padding: 12,
7420
+ };
7421
+ return (React.createElement("div", { className: clsx('seekora-suggestions-product-grid', className), style: style },
7422
+ React.createElement("div", { className: clsx('seekora-suggestions-product-grid-inner', gridClassName), style: gridStyle }, items.map((product, i) => {
7423
+ const globalIndex = productStartIndex >= 0 ? productStartIndex + i : i;
7424
+ const section = source === 'trending' ? 'products' : 'filtered_tab';
7425
+ const tabId = source !== 'trending' ? (source === 'tab' ? activeTabId : source) : undefined;
7426
+ return (React.createElement(ProductCard, { key: product.id ?? product.objectID ?? i, product: product, position: globalIndex, section: section, tabId: tabId, onSelect: () => selectProduct(product, globalIndex, section, tabId) }));
7427
+ }))));
7428
+ }
7429
+
7430
+ /**
7431
+ * CategoriesTabs – horizontal tabs (e.g. filtered tabs) (primitive)
7432
+ *
7433
+ * Active tab from context; on select updates context and tracks analytics.
7434
+ */
7435
+ function CategoriesTabs({ className, style, tabClassName }) {
7436
+ const { filteredTabs, activeTabId, setActiveTab } = useSuggestionsContext();
7437
+ if (filteredTabs.length === 0)
7438
+ return null;
7439
+ return (React.createElement("div", { className: clsx('seekora-suggestions-categories-tabs', className), style: {
7440
+ display: 'flex',
7441
+ gap: 4,
7442
+ padding: '8px 12px',
7443
+ borderBottom: '1px solid var(--seekora-border-color, #e5e7eb)',
7444
+ overflowX: 'auto',
7445
+ ...style,
7446
+ }, role: "tablist" }, filteredTabs.map((tab) => {
7447
+ const isActive = activeTabId === tab.id;
7448
+ return (React.createElement("button", { key: tab.id, type: "button", role: "tab", "aria-selected": isActive, className: clsx('seekora-suggestions-tab', isActive && 'seekora-suggestions-tab--active', tabClassName), style: {
7449
+ padding: '8px 12px',
7450
+ border: 'none',
7451
+ borderRadius: 'var(--seekora-border-radius, 6px)',
7452
+ backgroundColor: isActive ? 'var(--seekora-primary-light, rgba(59, 130, 246, 0.1))' : 'transparent',
7453
+ color: isActive ? 'var(--seekora-primary, #3b82f6)' : 'var(--seekora-text-primary, #111827)',
7454
+ cursor: 'pointer',
7455
+ fontSize: '0.875rem',
7456
+ fontWeight: isActive ? 600 : 400,
7457
+ whiteSpace: 'nowrap',
7458
+ transition: 'background-color 120ms ease',
7459
+ }, onClick: () => setActiveTab(tab) }, tab.label));
7460
+ })));
7461
+ }
7462
+
7463
+ /**
7464
+ * RecentSearchesList – list of recent queries (primitive)
7465
+ *
7466
+ * Reads recentSearches from context; each click calls selectRecentSearch. Optional title/render.
7467
+ */
7468
+ const itemStyle$1 = {
7469
+ padding: '10px 12px',
7470
+ cursor: 'pointer',
7471
+ border: 'none',
7472
+ width: '100%',
7473
+ textAlign: 'left',
7474
+ fontSize: 'inherit',
7475
+ fontFamily: 'inherit',
7476
+ backgroundColor: 'transparent',
7477
+ color: 'var(--seekora-text-primary, #111827)',
7478
+ transition: 'background-color 120ms ease',
7479
+ };
7480
+ function RecentSearchesList({ title = 'Recent', maxItems = 8, className, style, listClassName, renderItem, }) {
7481
+ const { recentSearches, query, selectRecentSearch } = useSuggestionsContext();
7482
+ const items = recentSearches.slice(0, maxItems);
7483
+ if (items.length === 0)
7484
+ return null;
7485
+ return (React.createElement("div", { className: clsx('seekora-suggestions-recent-list', className), style: style },
7486
+ title ? (React.createElement("div", { className: "seekora-suggestions-recent-title", style: { padding: '8px 12px', fontSize: '0.75rem', fontWeight: 600, color: 'var(--seekora-text-secondary, #6b7280)', textTransform: 'uppercase' } }, title)) : null,
7487
+ React.createElement("ul", { className: clsx('seekora-suggestions-recent-ul', listClassName), style: { margin: 0, padding: 0, listStyle: 'none' } }, items.map((search, i) => {
7488
+ const onSelect = () => selectRecentSearch(search);
7489
+ if (renderItem) {
7490
+ return React.createElement("li", { key: `${search.query}-${search.timestamp}` }, renderItem(search, i, onSelect));
7491
+ }
7492
+ return (React.createElement("li", { key: `${search.query}-${search.timestamp}` },
7493
+ React.createElement("button", { type: "button", className: "seekora-suggestions-recent-item", style: itemStyle$1, onMouseDown: (e) => {
7494
+ e.preventDefault();
7495
+ onSelect();
7496
+ } }, search.query)));
7497
+ }))));
7498
+ }
7499
+
7500
+ /**
7501
+ * TrendingList – list of trending searches (primitive)
7502
+ *
7503
+ * Reads trendingSearches from context; each click calls selectTrendingSearch. Optional title/render.
7504
+ */
7505
+ const itemStyle = {
7506
+ padding: '10px 12px',
7507
+ cursor: 'pointer',
7508
+ border: 'none',
7509
+ width: '100%',
7510
+ textAlign: 'left',
7511
+ fontSize: 'inherit',
7512
+ fontFamily: 'inherit',
7513
+ backgroundColor: 'transparent',
7514
+ color: 'var(--seekora-text-primary, #111827)',
7515
+ transition: 'background-color 120ms ease',
7516
+ };
7517
+ function TrendingList({ title = 'Trending', maxItems = 8, className, style, listClassName, renderItem, }) {
7518
+ const { trendingSearches, selectTrendingSearch } = useSuggestionsContext();
7519
+ const items = trendingSearches.slice(0, maxItems);
7520
+ if (items.length === 0)
7521
+ return null;
7522
+ return (React.createElement("div", { className: clsx('seekora-suggestions-trending-list', className), style: style },
7523
+ title ? (React.createElement("div", { className: "seekora-suggestions-trending-title", style: { padding: '8px 12px', fontSize: '0.75rem', fontWeight: 600, color: 'var(--seekora-text-secondary, #6b7280)', textTransform: 'uppercase' } }, title)) : null,
7524
+ React.createElement("ul", { className: clsx('seekora-suggestions-trending-ul', listClassName), style: { margin: 0, padding: 0, listStyle: 'none' } }, items.map((trending, i) => {
7525
+ const onSelect = () => selectTrendingSearch(trending, i);
7526
+ if (renderItem) {
7527
+ return React.createElement("li", { key: `${trending.query}-${i}` }, renderItem(trending, i, onSelect));
7528
+ }
7529
+ return (React.createElement("li", { key: `${trending.query}-${i}` },
7530
+ React.createElement("button", { type: "button", className: "seekora-suggestions-trending-item", style: itemStyle, onMouseDown: (e) => {
7531
+ e.preventDefault();
7532
+ onSelect();
7533
+ } },
7534
+ trending.query,
7535
+ trending.count != null ? (React.createElement("span", { style: { marginLeft: 8, color: 'var(--seekora-text-secondary, #6b7280)', fontSize: '0.875em' } }, trending.count)) : null)));
7536
+ }))));
7537
+ }
7538
+
7539
+ /**
7540
+ * SuggestionsError – error message (primitive)
7541
+ */
7542
+ function SuggestionsError({ className, style, render }) {
7543
+ const { error } = useSuggestionsContext();
7544
+ if (!error)
7545
+ return null;
7546
+ if (render)
7547
+ return React.createElement(React.Fragment, null, render(error));
7548
+ return (React.createElement("div", { className: clsx('seekora-suggestions-error', className), style: {
7549
+ padding: 16,
7550
+ color: 'var(--seekora-error, #dc2626)',
7551
+ fontSize: '0.875rem',
7552
+ ...style,
7553
+ } }, error.message));
7554
+ }
7555
+
7556
+ /**
7557
+ * SuggestionsDropdownComposition – reference composition
7558
+ *
7559
+ * Example layout built from primitives: SearchInput + DropdownPanel containing
7560
+ * RecentSearchesList (when query empty), SuggestionList, CategoriesTabs, ProductGrid, TrendingList.
7561
+ * Wrap with SearchProvider and SuggestionsProvider. Use as reference or replace
7562
+ * with your own arrangement of the same primitives.
7563
+ */
7564
+ function SuggestionsDropdownComposition({ showRecentSearches = true, showTrending = true, showTabs = true, showProducts = true, placeholder, ...providerProps }) {
7565
+ return (React.createElement(SuggestionsProvider, { ...providerProps },
7566
+ React.createElement("div", { className: "seekora-suggestions-dropdown-composition", style: { position: 'relative', width: '100%' } },
7567
+ React.createElement(SearchInput, { placeholder: placeholder }),
7568
+ React.createElement(DropdownPanel, null,
7569
+ React.createElement(SuggestionsError, null),
7570
+ React.createElement(SuggestionsLoading, null),
7571
+ showRecentSearches ? React.createElement(RecentSearchesList, null) : null,
7572
+ React.createElement(SuggestionList, null),
7573
+ showTabs ? React.createElement(CategoriesTabs, null) : null,
7574
+ showProducts ? React.createElement(ProductGrid, null) : null,
7575
+ showTrending ? React.createElement(TrendingList, null) : null))));
7576
+ }
7577
+
7578
+ /**
7579
+ * SectionSearchContext – preset query/filter section state
7580
+ *
7581
+ * For menus, sidebar, front-page blocks. Independent of main search state.
7582
+ */
7583
+ const SectionSearchContext = React.createContext(null);
7584
+ function useSectionSearchContext() {
7585
+ const context = React.useContext(SectionSearchContext);
7586
+ if (!context) {
7587
+ const error = new Error('useSectionSearchContext must be used within a SectionSearchProvider');
7588
+ log.error('SectionSearchContext: not available', { error: error.message });
7589
+ throw error;
7590
+ }
7591
+ return context;
7592
+ }
7593
+
7594
+ /**
7595
+ * SectionSearchProvider – preset query + filter section
7596
+ *
7597
+ * Runs client.search(query, { refinements, hitsPerPage, sortBy }) on mount and when
7598
+ * query/filters change. Does not use global SearchStateManager. Use for menus,
7599
+ * sidebar, front-page blocks (e.g. "New arrivals", "On sale").
7600
+ */
7601
+ function extractItems(response) {
7602
+ if (!response)
7603
+ return [];
7604
+ if (Array.isArray(response.results))
7605
+ return response.results;
7606
+ if (Array.isArray(response.hits))
7607
+ return response.hits;
7608
+ const data = response.data;
7609
+ if (data && Array.isArray(data.results))
7610
+ return data.results;
7611
+ if (data && Array.isArray(data.data))
7612
+ return data.data;
7613
+ return [];
7614
+ }
7615
+ function extractTotal(response) {
7616
+ if (!response)
7617
+ return 0;
7618
+ const n = response.totalResults ?? response.total ?? response.total_results;
7619
+ if (typeof n === 'number')
7620
+ return n;
7621
+ const data = response.data;
7622
+ if (data?.total_results != null)
7623
+ return Number(data.total_results);
7624
+ if (data?.data?.total_results != null)
7625
+ return Number(data.data.total_results);
7626
+ return 0;
7627
+ }
7628
+ function SectionSearchProvider({ children, query, refinements = [], maxItems = 12, sortBy, enabled = true, sectionId, }) {
7629
+ const { client } = useSearchContext();
7630
+ const [items, setItems] = React.useState([]);
7631
+ const [loading, setLoading] = React.useState(true);
7632
+ const [error, setError] = React.useState(null);
7633
+ const [totalCount, setTotalCount] = React.useState(0);
7634
+ React.useEffect(() => {
7635
+ if (!enabled || !client?.search) {
7636
+ setItems([]);
7637
+ setLoading(false);
7638
+ setError(null);
7639
+ setTotalCount(0);
7640
+ return;
7641
+ }
7642
+ let cancelled = false;
7643
+ setLoading(true);
7644
+ setError(null);
7645
+ const options = {
7646
+ per_page: maxItems,
7647
+ page: 1,
7648
+ };
7649
+ if (sortBy)
7650
+ options.sort_by = sortBy;
7651
+ if (refinements.length > 0) {
7652
+ options.filter_by = refinements.map((r) => `${r.field}:${r.value}`).join(',');
7653
+ }
7654
+ client
7655
+ .search(query, options)
7656
+ .then((response) => {
7657
+ if (cancelled)
7658
+ return;
7659
+ setItems(extractItems(response));
7660
+ setTotalCount(extractTotal(response));
7661
+ setLoading(false);
7662
+ })
7663
+ .catch((err) => {
7664
+ if (cancelled)
7665
+ return;
7666
+ setError(err instanceof Error ? err : new Error(String(err)));
7667
+ setItems([]);
7668
+ setLoading(false);
7669
+ });
7670
+ return () => {
7671
+ cancelled = true;
7672
+ };
7673
+ }, [client, enabled, query, maxItems, sortBy, refinements]);
7674
+ const trackClick = React.useCallback((item, position) => {
7675
+ if (!client?.trackEvent)
7676
+ return;
7677
+ const id = item?.id ?? item?.objectID;
7678
+ client.trackEvent({
7679
+ event_name: 'section_result_click',
7680
+ clicked_item_id: id,
7681
+ position,
7682
+ section: sectionId,
7683
+ metadata: { section_id: sectionId },
7684
+ }, undefined);
7685
+ }, [client, sectionId]);
7686
+ const value = React.useMemo(() => ({
7687
+ items,
7688
+ loading,
7689
+ error,
7690
+ totalCount,
7691
+ sectionId,
7692
+ trackClick,
7693
+ }), [items, loading, error, totalCount, sectionId, trackClick]);
7694
+ return React.createElement(SectionSearchContext.Provider, { value: value }, children);
7695
+ }
7696
+
7697
+ /**
7698
+ * SectionLoading – loading state for section (primitive)
7699
+ */
7700
+ function SectionLoading({ className, style, text = 'Loading...' }) {
7701
+ const { loading } = useSectionSearchContext();
7702
+ if (!loading)
7703
+ return null;
7704
+ return (React.createElement("div", { className: className, style: { padding: 16, color: 'var(--seekora-text-secondary)', fontSize: '0.875rem', ...style } }, text));
7705
+ }
7706
+
7707
+ /**
7708
+ * SectionError – error state for section (primitive)
7709
+ */
7710
+ function SectionError({ className, style, render }) {
7711
+ const { error } = useSectionSearchContext();
7712
+ if (!error)
7713
+ return null;
7714
+ if (render)
7715
+ return React.createElement(React.Fragment, null, render(error));
7716
+ return (React.createElement("div", { className: className, style: { padding: 16, color: 'var(--seekora-error,#dc2626)', fontSize: '0.875rem', ...style } }, error.message));
7717
+ }
7718
+
7719
+ /**
7720
+ * SectionItemGrid – generic grid of items from SectionSearchProvider (primitive)
7721
+ */
7722
+ function SectionItemGrid({ columns = 4, maxItems = 12, className, style, getItemId = (i) => i.id ?? String(i?.objectID ?? ''), getItemTitle = (i) => i.title ?? i?.title ?? '', getItemImage = (i) => i.image ?? i?.image, getItemDescription = (i) => i.description ?? i?.description, getItemUrl = (i) => i.url ?? i?.url, renderItem, }) {
7723
+ const { items, loading, error, trackClick } = useSectionSearchContext();
7724
+ if (loading)
7725
+ return React.createElement(SectionLoading, { className: className, style: style });
7726
+ if (error)
7727
+ return React.createElement(SectionError, { className: className, style: style });
7728
+ return (React.createElement(ItemGrid, { items: items, maxItems: maxItems, columns: columns, className: className, style: style, getItemId: getItemId, getItemTitle: getItemTitle, getItemImage: getItemImage, getItemDescription: getItemDescription, getItemUrl: getItemUrl, renderItem: renderItem, onItemClick: (item, index) => trackClick(item, index) }));
7729
+ }
7730
+
7731
+ /**
7732
+ * ProductGallery – product detail image gallery (primitive)
7733
+ *
7734
+ * Uses ImageDisplay with configurable variant (carousel, thumbStrip, etc.).
7735
+ * For use on individual product page.
7736
+ */
7737
+ function ProductGallery({ images, variant = 'thumbStrip', alt = 'Product', className, style, carouselAutoplay, carouselIntervalMs, }) {
7738
+ return (React.createElement("div", { className: clsx('seekora-product-gallery', className), style: style },
7739
+ React.createElement(ImageDisplay, { images: images, variant: variant, alt: alt, carouselAutoplay: carouselAutoplay, carouselIntervalMs: carouselIntervalMs })));
7740
+ }
7741
+
7742
+ /**
7743
+ * ProductInfo – product detail block (primitive)
7744
+ *
7745
+ * Title, description, price, optional variant selector and CTA. Minimal layout;
7746
+ * override with className/style. For use on individual product page.
7747
+ */
7748
+ function ProductInfo({ title, description, price, currency = '$', renderVariantSelector, renderCTA, className, style, }) {
7749
+ const priceNum = price != null ? (typeof price === 'number' ? price : parseFloat(String(price))) : null;
7750
+ return (React.createElement("div", { className: clsx('seekora-product-info', className), style: { display: 'flex', flexDirection: 'column', gap: 12, ...style } },
7751
+ React.createElement("h1", { className: "seekora-product-info-title", style: { fontSize: '1.25rem', fontWeight: 600, margin: 0 } }, title),
7752
+ priceNum != null && !Number.isNaN(priceNum) ? (React.createElement("span", { className: "seekora-product-info-price", style: { fontSize: '1.125rem', fontWeight: 600 } },
7753
+ currency,
7754
+ priceNum.toFixed(2))) : null,
7755
+ description ? (React.createElement("p", { className: "seekora-product-info-description", style: { fontSize: '0.875rem', color: 'var(--seekora-text-secondary)', margin: 0, lineHeight: 1.5 } }, description)) : null,
7756
+ renderVariantSelector?.(),
7757
+ renderCTA?.()));
7758
+ }
7759
+
7760
+ /**
7761
+ * ProductRecommendations – related / frequently bought (primitive)
7762
+ *
7763
+ * Renders a section of recommended items (generic ItemGrid or product list).
7764
+ * Pass items and onItemClick; or wrap SectionSearchProvider with preset query for "related".
7765
+ * For use on individual product page.
7766
+ */
7767
+ function ProductRecommendations({ title = 'You may also like', items, onItemClick, maxItems = 6, columns = 3, className, style, renderItem, }) {
7768
+ if (!items?.length)
7769
+ return null;
7770
+ return (React.createElement("div", { className: clsx('seekora-product-recommendations', className), style: style },
7771
+ React.createElement("h2", { className: "seekora-product-recommendations-title", style: { fontSize: '1rem', fontWeight: 600, marginBottom: 12 } }, title),
7772
+ React.createElement(ItemGrid, { items: items, maxItems: maxItems, columns: columns, onItemClick: onItemClick, renderItem: renderItem })));
7773
+ }
7774
+
6708
7775
  /**
6709
7776
  * Utility functions for Query Suggestions components
6710
7777
  */
@@ -13300,10 +14367,12 @@ function updateSuggestionsStyles(theme) {
13300
14367
 
13301
14368
  exports.AmazonDropdown = AmazonDropdown;
13302
14369
  exports.Breadcrumb = Breadcrumb;
14370
+ exports.CategoriesTabs = CategoriesTabs;
13303
14371
  exports.ClearRefinements = ClearRefinements;
13304
14372
  exports.CurrentRefinements = CurrentRefinements;
13305
14373
  exports.DocSearch = DocSearch;
13306
14374
  exports.DocSearchButton = DocSearchButton;
14375
+ exports.DropdownPanel = DropdownPanel;
13307
14376
  exports.Facets = Facets;
13308
14377
  exports.FederatedDropdown = FederatedDropdown;
13309
14378
  exports.Fingerprint = Fingerprint;
@@ -13312,33 +14381,54 @@ exports.GoogleDropdown = GoogleDropdown;
13312
14381
  exports.HierarchicalMenu = HierarchicalMenu;
13313
14382
  exports.Highlight = Highlight$1;
13314
14383
  exports.HitsPerPage = HitsPerPage;
14384
+ exports.ImageDisplay = ImageDisplay;
13315
14385
  exports.InfiniteHits = InfiniteHits;
14386
+ exports.ItemCard = ItemCard;
14387
+ exports.ItemGrid = ItemGrid;
13316
14388
  exports.MinimalDropdown = MinimalDropdown;
13317
14389
  exports.MobileFilters = MobileFilters;
13318
14390
  exports.MobileFiltersButton = MobileFiltersButton;
13319
14391
  exports.MobileSheetDropdown = MobileSheetDropdown;
13320
14392
  exports.Pagination = Pagination;
13321
14393
  exports.PinterestDropdown = PinterestDropdown;
14394
+ exports.ProductCard = ProductCard;
14395
+ exports.ProductGallery = ProductGallery;
14396
+ exports.ProductGrid = ProductGrid;
14397
+ exports.ProductInfo = ProductInfo;
14398
+ exports.ProductRecommendations = ProductRecommendations;
13322
14399
  exports.QuerySuggestions = QuerySuggestions;
13323
14400
  exports.QuerySuggestionsDropdown = QuerySuggestionsDropdown;
13324
14401
  exports.RangeInput = RangeInput;
13325
14402
  exports.RangeSlider = RangeSlider;
14403
+ exports.RecentSearchesList = RecentSearchesList;
13326
14404
  exports.RecentlyViewed = RecentlyViewed;
13327
14405
  exports.RelatedProducts = RelatedProducts;
13328
14406
  exports.RichQuerySuggestions = RichQuerySuggestions;
13329
14407
  exports.SearchBar = SearchBar;
13330
14408
  exports.SearchBarWithSuggestions = SearchBarWithSuggestions;
14409
+ exports.SearchInput = SearchInput;
13331
14410
  exports.SearchLayout = SearchLayout;
13332
14411
  exports.SearchProvider = SearchProvider;
13333
14412
  exports.SearchResults = SearchResults;
14413
+ exports.SectionError = SectionError;
14414
+ exports.SectionItemGrid = SectionItemGrid;
14415
+ exports.SectionLoading = SectionLoading;
14416
+ exports.SectionSearchProvider = SectionSearchProvider;
13334
14417
  exports.ShopifyDropdown = ShopifyDropdown;
13335
14418
  exports.Snippet = Snippet;
13336
14419
  exports.SortBy = SortBy;
13337
14420
  exports.SpotlightDropdown = SpotlightDropdown;
13338
14421
  exports.Stats = Stats;
13339
14422
  exports.SuggestionDropdownVariants = SuggestionDropdownVariants;
14423
+ exports.SuggestionItem = SuggestionItem;
14424
+ exports.SuggestionList = SuggestionList;
13340
14425
  exports.SuggestionSearchBar = SuggestionSearchBar;
14426
+ exports.SuggestionsDropdownComposition = SuggestionsDropdownComposition;
14427
+ exports.SuggestionsError = SuggestionsError;
14428
+ exports.SuggestionsLoading = SuggestionsLoading;
14429
+ exports.SuggestionsProvider = SuggestionsProvider;
13341
14430
  exports.TrendingItems = TrendingItems;
14431
+ exports.TrendingList = TrendingList;
13342
14432
  exports.addRecentSearch = addRecentSearch;
13343
14433
  exports.addToRecentlyViewed = addToRecentlyViewed;
13344
14434
  exports.brandPresets = brandPresets;
@@ -13384,7 +14474,9 @@ exports.useQuerySuggestionsEnhanced = useQuerySuggestionsEnhanced;
13384
14474
  exports.useResponsive = useResponsive;
13385
14475
  exports.useSearchContext = useSearchContext;
13386
14476
  exports.useSearchState = useSearchState;
14477
+ exports.useSectionSearchContext = useSectionSearchContext;
13387
14478
  exports.useSeekoraSearch = useSeekoraSearch;
13388
14479
  exports.useSmartSuggestions = useSmartSuggestions;
13389
14480
  exports.useSuggestionsAnalytics = useSuggestionsAnalytics;
14481
+ exports.useSuggestionsContext = useSuggestionsContext;
13390
14482
  //# sourceMappingURL=index.js.map