@seekora-ai/ui-sdk-react 0.2.7 → 0.2.9

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/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,
@@ -4566,7 +4567,7 @@ function transformFilteredTab(raw) {
4566
4567
  // Main Hook
4567
4568
  // ============================================================================
4568
4569
  function useQuerySuggestionsEnhanced(options) {
4569
- const { client, query, enabled = true, debounceMs = 200, maxSuggestions = 10, minQueryLength = 1, includeDropdownRecommendations = false, includeCategories = true, includeFacets = false, maxCategories = 3, maxFacets = 5, filteredTabs, minPopularity, timeRange, disableTypoTolerance, analyticsTags, enableRecentSearches = true, maxRecentSearches = MAX_RECENT_SEARCHES_DEFAULT, recentSearchesKey = RECENT_SEARCHES_DEFAULT_KEY, onSuggestionsLoaded, onError, } = options;
4570
+ const { client, query, enabled = true, debounceMs = 200, maxSuggestions = 10, minQueryLength = 1, includeDropdownRecommendations = false, includeDropdownProductList = true, includeFilteredTabs = true, includeCategories = true, includeFacets = false, maxCategories = 3, maxFacets = 5, filteredTabs, minPopularity, timeRange, disableTypoTolerance, analyticsTags, enableRecentSearches = true, maxRecentSearches = MAX_RECENT_SEARCHES_DEFAULT, recentSearchesKey = RECENT_SEARCHES_DEFAULT_KEY, onSuggestionsLoaded, onError, } = options;
4570
4571
  // State
4571
4572
  const [suggestions, setSuggestions] = React.useState([]);
4572
4573
  const [loading, setLoading] = React.useState(false);
@@ -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;
@@ -4610,6 +4614,8 @@ function useQuerySuggestionsEnhanced(options) {
4610
4614
  const response = await client.getSuggestions?.(searchQuery, {
4611
4615
  hitsPerPage: maxSuggestions,
4612
4616
  include_dropdown_recommendations: includeDropdownRecommendations || (filteredTabs && filteredTabs.length > 0),
4617
+ include_dropdown_product_list: includeDropdownProductList,
4618
+ include_filtered_tabs: includeFilteredTabs,
4613
4619
  include_categories: includeCategories,
4614
4620
  include_facets: includeFacets,
4615
4621
  max_categories: maxCategories,
@@ -4629,12 +4635,17 @@ function useQuerySuggestionsEnhanced(options) {
4629
4635
  setSuggestions(transformedSuggestions);
4630
4636
  // Extract dropdown recommendations from extensions
4631
4637
  const extensions = (response.extensions || {});
4638
+ const rawResults = response.results;
4639
+ const secondResultHits = Array.isArray(rawResults?.[1]?.hits) ? rawResults[1].hits : [];
4640
+ // Multi-index response: results[0]=suggestions, results[1]=product hits; expose results[1].hits as product_hits for fallback when extensions have no products
4641
+ const productHits = secondResultHits.length > 0 ? secondResultHits.map((h) => transformProduct(h)) : [];
4632
4642
  const recommendations = {
4633
4643
  trending_searches: Array.isArray(extensions.trending_searches) ? extensions.trending_searches : [],
4634
4644
  top_searches: Array.isArray(extensions.top_searches) ? extensions.top_searches : [],
4635
4645
  related_searches: Array.isArray(extensions.related_searches) ? extensions.related_searches : [],
4636
4646
  trending_products: Array.isArray(extensions.trending_products) ? extensions.trending_products.map(transformProduct) : [],
4637
4647
  item_recommendations: Array.isArray(extensions.item_recommendations) ? extensions.item_recommendations.map(transformProduct) : [],
4648
+ product_hits: productHits.length > 0 ? productHits : undefined,
4638
4649
  popular_brands: Array.isArray(extensions.popular_brands) ? extensions.popular_brands : [],
4639
4650
  filtered_tabs: Array.isArray(extensions.filtered_tabs) ? extensions.filtered_tabs.map(transformFilteredTab) : [],
4640
4651
  processing_time_ms: typeof extensions.processing_time_ms === 'number' ? extensions.processing_time_ms : undefined,
@@ -4694,8 +4705,11 @@ function useQuerySuggestionsEnhanced(options) {
4694
4705
  }
4695
4706
  }, [
4696
4707
  client,
4708
+ minQueryLength,
4697
4709
  maxSuggestions,
4698
4710
  includeDropdownRecommendations,
4711
+ includeDropdownProductList,
4712
+ includeFilteredTabs,
4699
4713
  includeCategories,
4700
4714
  includeFacets,
4701
4715
  maxCategories,
@@ -4770,7 +4784,19 @@ function useQuerySuggestionsEnhanced(options) {
4770
4784
  const relatedSearches = dropdownRecommendations?.related_searches || [];
4771
4785
  const popularBrands = dropdownRecommendations?.popular_brands || [];
4772
4786
  const filteredTabsResult = dropdownRecommendations?.filtered_tabs || [];
4773
- const trendingProducts = dropdownRecommendations?.trending_products || [];
4787
+ // 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
4788
+ const trendingProducts = React.useMemo(() => {
4789
+ const fromTrending = dropdownRecommendations?.trending_products;
4790
+ if (fromTrending && fromTrending.length > 0)
4791
+ return fromTrending;
4792
+ const fromItemRecs = dropdownRecommendations?.item_recommendations;
4793
+ if (fromItemRecs && fromItemRecs.length > 0)
4794
+ return fromItemRecs;
4795
+ const firstTab = dropdownRecommendations?.filtered_tabs?.[0];
4796
+ if (firstTab?.products && firstTab.products.length > 0)
4797
+ return firstTab.products;
4798
+ return dropdownRecommendations?.product_hits ?? [];
4799
+ }, [dropdownRecommendations?.trending_products, dropdownRecommendations?.item_recommendations, dropdownRecommendations?.filtered_tabs, dropdownRecommendations?.product_hits]);
4774
4800
  const hasContent = React.useMemo(() => {
4775
4801
  return (suggestions.length > 0 ||
4776
4802
  recentSearches.length > 0 ||
@@ -5453,7 +5479,7 @@ const CloseIcon = () => (React.createElement("svg", { viewBox: "0 0 20 20", fill
5453
5479
  // Component
5454
5480
  // ============================================================================
5455
5481
  const RichQuerySuggestions = React.forwardRef(function RichQuerySuggestions(props, ref) {
5456
- const { query, isOpen = true, sections = DEFAULT_SECTIONS, maxSuggestionsPerSection = 8, minQueryLength = 0, debounceMs = 200, includeDropdownRecommendations = true, includeCategories = true, maxCategories = 3, showCounts = true, showCategoryCounts = true, showSectionHeaders = true, classNames = {}, style, renderSuggestion, renderCategory, renderTrendingItem, renderRecentItem, header, footer, width = '100%', maxHeight = '480px', zIndex = 1000, ariaLabel = 'Search suggestions', analyticsTags, onSuggestionSelect, onCategoryClick, onRecentSearchClick, onRecentSearchRemove, onViewAllClick, onOpen, onClose, } = props;
5482
+ const { query, isOpen = true, sections = DEFAULT_SECTIONS, maxSuggestionsPerSection = 8, minQueryLength = 0, debounceMs = 200, includeDropdownRecommendations = true, includeDropdownProductList = true, includeFilteredTabs = true, includeCategories = true, maxCategories = 3, showCounts = true, showCategoryCounts = true, showSectionHeaders = true, classNames = {}, style, renderSuggestion, renderCategory, renderTrendingItem, renderRecentItem, header, footer, width = '100%', maxHeight = '480px', zIndex = 1000, ariaLabel = 'Search suggestions', analyticsTags, onSuggestionSelect, onCategoryClick, onRecentSearchClick, onRecentSearchRemove, onViewAllClick, onOpen, onClose, } = props;
5457
5483
  const { client } = useSearchContext();
5458
5484
  const containerRef = React.useRef(null);
5459
5485
  const [activeIndex, setActiveIndex] = React.useState(-1);
@@ -5467,6 +5493,8 @@ const RichQuerySuggestions = React.forwardRef(function RichQuerySuggestions(prop
5467
5493
  maxSuggestions: maxSuggestionsPerSection,
5468
5494
  minQueryLength,
5469
5495
  includeDropdownRecommendations,
5496
+ includeDropdownProductList,
5497
+ includeFilteredTabs,
5470
5498
  includeCategories,
5471
5499
  maxCategories,
5472
5500
  analyticsTags,
@@ -6971,15 +6999,17 @@ const inputStyles = {
6971
6999
  color: 'var(--seekora-text-primary, #111827)',
6972
7000
  fontFamily: 'inherit',
6973
7001
  };
6974
- function SearchInput({ placeholder = 'Search...', autoFocus = false, showClearButton = true, className, style, inputClassName, inputStyle, ariaLabel = 'Search', }) {
7002
+ function SearchInput({ placeholder = 'Search...', autoFocus = false, showClearButton = true, closeOnBlur = true, leftIcon, className, style, inputClassName, inputStyle, ariaLabel = 'Search', }) {
6975
7003
  const { query, setQuery, isOpen, setIsOpen, navigateNext, navigatePrev, selectActive, close, } = useSuggestionsContext();
6976
7004
  const inputRef = React.useRef(null);
6977
7005
  const handleFocus = React.useCallback(() => {
6978
7006
  setIsOpen(true);
6979
7007
  }, [setIsOpen]);
6980
7008
  const handleBlur = React.useCallback(() => {
7009
+ if (!closeOnBlur)
7010
+ return;
6981
7011
  setTimeout(() => close(), 200);
6982
- }, [close]);
7012
+ }, [close, closeOnBlur]);
6983
7013
  const handleChange = React.useCallback((e) => {
6984
7014
  setQuery(e.target.value);
6985
7015
  }, [setQuery]);
@@ -7012,6 +7042,7 @@ function SearchInput({ placeholder = 'Search...', autoFocus = false, showClearBu
7012
7042
  }, [setQuery]);
7013
7043
  return (React.createElement("div", { className: clsx('seekora-suggestions-search-input-wrapper', className), style: { ...defaultStyles, ...style } },
7014
7044
  React.createElement("div", { className: "seekora-suggestions-input-wrapper", style: inputWrapperStyles },
7045
+ leftIcon ? (React.createElement("span", { className: "seekora-suggestions-input-left-icon", style: { display: 'flex', flexShrink: 0, color: 'var(--seekora-text-secondary, #6b7280)' } }, leftIcon)) : null,
7015
7046
  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 } }),
7016
7047
  showClearButton && query ? (React.createElement("button", { type: "button", onClick: handleClear, className: "seekora-suggestions-input-clear", "aria-label": "Clear search", style: {
7017
7048
  padding: 4,
@@ -11767,7 +11798,7 @@ const createStyles = (isMobile) => ({
11767
11798
  // Component
11768
11799
  // ============================================================================
11769
11800
  const SuggestionSearchBar = React.forwardRef(function SuggestionSearchBar(props, ref) {
11770
- const { client, variant = 'amazon', autoMobileVariant = true, placeholder = 'Search...', defaultQuery = '', value, minQueryLength = 1, maxSuggestions = 8, debounceMs = 200, includeDropdownRecommendations = true, includeCategories = true, filteredTabs, analyticsTags, enableRecentSearches = true, maxRecentSearches = 10, showProducts = true, showTrendingOnEmpty = true, enableAnalytics = true, analyticsConfig, suggestionFields, productFields, theme, onSearch, onQueryChange, onSuggestionSelect, onProductClick, onCategoryClick, onTabChange, className, style, inputClassName, dropdownWidth, dropdownMaxHeight = '500px', zIndex = 1000, enableCache = true, cacheTtlMs = 30000, cacheMaxSize = 100, } = props;
11801
+ const { client, variant = 'amazon', autoMobileVariant = true, placeholder = 'Search...', defaultQuery = '', value, minQueryLength = 1, maxSuggestions = 8, debounceMs = 200, includeDropdownRecommendations = true, includeDropdownProductList = true, includeFilteredTabs = true, includeCategories = true, filteredTabs, analyticsTags, enableRecentSearches = true, maxRecentSearches = 10, showProducts = true, showTrendingOnEmpty = true, enableAnalytics = true, analyticsConfig, suggestionFields, productFields, theme, onSearch, onQueryChange, onSuggestionSelect, onProductClick, onCategoryClick, onTabChange, className, style, inputClassName, dropdownWidth, dropdownMaxHeight = '500px', zIndex = 1000, enableCache = true, cacheTtlMs = 30000, cacheMaxSize = 100, } = props;
11771
11802
  // Theme: prop overrides context (SearchProvider theme)
11772
11803
  const searchContext = useSearchContext();
11773
11804
  const effectiveTheme = theme ?? searchContext.theme;
@@ -11850,6 +11881,8 @@ const SuggestionSearchBar = React.forwardRef(function SuggestionSearchBar(props,
11850
11881
  const cacheOptions = {
11851
11882
  maxSuggestions,
11852
11883
  includeDropdownRecommendations,
11884
+ includeDropdownProductList,
11885
+ includeFilteredTabs,
11853
11886
  includeCategories,
11854
11887
  filteredTabs: filteredTabs?.map(t => t.filter).join(','),
11855
11888
  };
@@ -11870,6 +11903,8 @@ const SuggestionSearchBar = React.forwardRef(function SuggestionSearchBar(props,
11870
11903
  const response = await client.getSuggestions?.(searchQuery, {
11871
11904
  hitsPerPage: maxSuggestions,
11872
11905
  include_dropdown_recommendations: includeDropdownRecommendations,
11906
+ include_dropdown_product_list: includeDropdownProductList,
11907
+ include_filtered_tabs: includeFilteredTabs,
11873
11908
  include_categories: includeCategories,
11874
11909
  filtered_tabs: filteredTabs,
11875
11910
  analytics_tags: analyticsTags,
@@ -11908,7 +11943,7 @@ const SuggestionSearchBar = React.forwardRef(function SuggestionSearchBar(props,
11908
11943
  finally {
11909
11944
  setLoading(false);
11910
11945
  }
11911
- }, [client, minQueryLength, maxSuggestions, includeDropdownRecommendations, includeCategories, filteredTabs, analyticsTags, enableAnalytics, analytics, cache]);
11946
+ }, [client, minQueryLength, maxSuggestions, includeDropdownRecommendations, includeDropdownProductList, includeFilteredTabs, includeCategories, filteredTabs, analyticsTags, enableAnalytics, analytics, cache]);
11912
11947
  // Parse API response - handles multiple response formats
11913
11948
  const parseAndSetData = React.useCallback((response) => {
11914
11949
  // Handle different response structures from the API/SDK