@seekora-ai/ui-sdk-react 0.2.25 → 0.2.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Facets.d.ts +2 -0
- package/dist/components/Facets.d.ts.map +1 -1
- package/dist/components/Facets.js +16 -3
- package/dist/components/primitives/ImageDisplay.js +3 -3
- package/dist/components/suggestions/types.d.ts +2 -0
- package/dist/components/suggestions/types.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ProductCardLayouts.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ProductCardLayouts.js +6 -6
- package/dist/hooks/useFilters.d.ts.map +1 -1
- package/dist/hooks/useFilters.js +15 -4
- package/dist/index.umd.js +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.esm.js +43 -25
- package/dist/src/index.esm.js.map +1 -1
- package/dist/src/index.js +43 -25
- package/dist/src/index.js.map +1 -1
- package/dist/utils/responsive.d.ts.map +1 -1
- package/dist/utils/responsive.js +4 -10
- package/package.json +3 -3
package/dist/src/index.js
CHANGED
|
@@ -2711,11 +2711,8 @@ function getCurrentBreakpoint(width) {
|
|
|
2711
2711
|
* Hook to get current breakpoint
|
|
2712
2712
|
*/
|
|
2713
2713
|
function useBreakpoint() {
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
return 'lg';
|
|
2717
|
-
return getCurrentBreakpoint(window.innerWidth);
|
|
2718
|
-
});
|
|
2714
|
+
// Always start with 'lg' to avoid hydration mismatch between server and client
|
|
2715
|
+
const [breakpoint, setBreakpoint] = React.useState('lg');
|
|
2719
2716
|
React.useEffect(() => {
|
|
2720
2717
|
const handleResize = () => {
|
|
2721
2718
|
setBreakpoint(getCurrentBreakpoint(window.innerWidth));
|
|
@@ -2730,11 +2727,8 @@ function useBreakpoint() {
|
|
|
2730
2727
|
* Hook to check if current viewport matches breakpoint
|
|
2731
2728
|
*/
|
|
2732
2729
|
function useMediaQuery(query) {
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
return false;
|
|
2736
|
-
return window.matchMedia(query).matches;
|
|
2737
|
-
});
|
|
2730
|
+
// Always start with false to avoid hydration mismatch between server and client
|
|
2731
|
+
const [matches, setMatches] = React.useState(false);
|
|
2738
2732
|
React.useEffect(() => {
|
|
2739
2733
|
const mediaQuery = window.matchMedia(query);
|
|
2740
2734
|
const handleChange = () => setMatches(mediaQuery.matches);
|
|
@@ -3530,15 +3524,22 @@ const useFilters = (options) => {
|
|
|
3530
3524
|
const [error, setError] = React.useState(null);
|
|
3531
3525
|
const mountedRef = React.useRef(true);
|
|
3532
3526
|
const autoFetch = options?.autoFetch !== false;
|
|
3527
|
+
// Track whether we've completed the first fetch (to avoid skeleton flash on refetches)
|
|
3528
|
+
const hasDataRef = React.useRef(false);
|
|
3533
3529
|
// Extract non-autoFetch options to pass to fetchFilters
|
|
3534
3530
|
const fetchFilters = React.useCallback(async () => {
|
|
3535
|
-
|
|
3531
|
+
// Only show loading spinner on the very first fetch; subsequent refetches
|
|
3532
|
+
// keep previous data visible to avoid skeleton/flash on facet changes.
|
|
3533
|
+
if (!hasDataRef.current) {
|
|
3534
|
+
setLoading(true);
|
|
3535
|
+
}
|
|
3536
3536
|
setError(null);
|
|
3537
3537
|
try {
|
|
3538
3538
|
const { autoFetch: _, ...filterOptions } = options || {};
|
|
3539
3539
|
const response = await stateManager.fetchFilters(filterOptions);
|
|
3540
3540
|
if (mountedRef.current) {
|
|
3541
3541
|
setFilters(response?.filters || []);
|
|
3542
|
+
hasDataRef.current = true;
|
|
3542
3543
|
setLoading(false);
|
|
3543
3544
|
}
|
|
3544
3545
|
}
|
|
@@ -3549,14 +3550,18 @@ const useFilters = (options) => {
|
|
|
3549
3550
|
}
|
|
3550
3551
|
}
|
|
3551
3552
|
}, [stateManager, options?.facetBy, options?.maxFacetValues, options?.disjunctiveFacets?.join(',')]);
|
|
3552
|
-
// Track query
|
|
3553
|
-
|
|
3553
|
+
// Track query to only refetch filters when query actually changes.
|
|
3554
|
+
// Sentinel ensures the first subscribe callback always triggers a fetch.
|
|
3555
|
+
const prevKeyRef = React.useRef(null);
|
|
3554
3556
|
// Refetch when query or refinements change (not on every state update)
|
|
3555
3557
|
React.useEffect(() => {
|
|
3556
3558
|
if (!autoFetch)
|
|
3557
3559
|
return;
|
|
3558
3560
|
const unsubscribe = stateManager.subscribe((state) => {
|
|
3559
|
-
|
|
3561
|
+
// Only track query changes — refinements are NOT passed to the Filters API
|
|
3562
|
+
// (facets are generated from search query only, not narrowed by active filters).
|
|
3563
|
+
// This prevents redundant filters refetches on every facet toggle.
|
|
3564
|
+
const key = state.query;
|
|
3560
3565
|
if (key === prevKeyRef.current)
|
|
3561
3566
|
return;
|
|
3562
3567
|
prevKeyRef.current = key;
|
|
@@ -3906,7 +3911,7 @@ const CSS_VAR_DEFAULTS = {
|
|
|
3906
3911
|
// ---------------------------------------------------------------------------
|
|
3907
3912
|
// Component
|
|
3908
3913
|
// ---------------------------------------------------------------------------
|
|
3909
|
-
const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, renderFacet, renderFacetItem, maxItems = 10, showMore = true, className, style, theme: customTheme, variant = 'checkbox', searchable = false, showCounts = true, colorMap, defaultCollapsed = false, size = 'medium', facetRanges, useFiltersApi = false, disjunctiveFacets, hideEmptyFacets = true, defaultCollapsedFields, }) => {
|
|
3914
|
+
const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, renderFacet, renderFacetItem, maxItems = 10, showMore = true, className, style, theme: customTheme, variant = 'checkbox', searchable = false, showCounts = true, colorMap, defaultCollapsed = false, size = 'medium', facetRanges, useFiltersApi = false, disjunctiveFacets, hideEmptyFacets = true, defaultCollapsedFields, onFacetsAvailable, }) => {
|
|
3910
3915
|
const { theme } = useSearchContext();
|
|
3911
3916
|
const { results: stateResults, refinements, addRefinement, removeRefinement } = useSearchState();
|
|
3912
3917
|
const facetsTheme = customTheme || {};
|
|
@@ -3984,9 +3989,22 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
|
|
|
3984
3989
|
return extracted;
|
|
3985
3990
|
};
|
|
3986
3991
|
const rawFacetList = extractFacets();
|
|
3992
|
+
const hasStats = (stats) => stats != null && (stats.min != null || stats.max != null);
|
|
3987
3993
|
const facets = hideEmptyFacets
|
|
3988
|
-
? rawFacetList.filter(f => f.items.length > 0 || f.stats
|
|
3994
|
+
? rawFacetList.filter(f => f.items.length > 0 || hasStats(f.stats))
|
|
3989
3995
|
: rawFacetList;
|
|
3996
|
+
// Notify parent about facet availability
|
|
3997
|
+
const facetCount = facets.length;
|
|
3998
|
+
const prevFacetAvailableRef = React.useRef(null);
|
|
3999
|
+
React.useEffect(() => {
|
|
4000
|
+
if (!onFacetsAvailable)
|
|
4001
|
+
return;
|
|
4002
|
+
const available = facetCount > 0;
|
|
4003
|
+
if (prevFacetAvailableRef.current !== available) {
|
|
4004
|
+
prevFacetAvailableRef.current = available;
|
|
4005
|
+
onFacetsAvailable(available);
|
|
4006
|
+
}
|
|
4007
|
+
}, [facetCount, onFacetsAvailable]);
|
|
3990
4008
|
// -------------------------------------------------------------------
|
|
3991
4009
|
// Handlers
|
|
3992
4010
|
// -------------------------------------------------------------------
|
|
@@ -10329,7 +10347,7 @@ function ImageDisplay({ images, variant = 'single', alt = '', className, style,
|
|
|
10329
10347
|
if (variant === 'hover') {
|
|
10330
10348
|
const showSecond = safeImages.length > 1 && hovering;
|
|
10331
10349
|
const src = showSecond ? safeImages[1] : safeImages[0];
|
|
10332
|
-
const hoverImgStyle = style?.aspectRatio ? { ...imgBaseStyle, aspectRatio: style.aspectRatio } : imgBaseStyle;
|
|
10350
|
+
const hoverImgStyle = style?.aspectRatio || style?.objectFit ? { ...imgBaseStyle, ...(style.aspectRatio ? { aspectRatio: style.aspectRatio } : {}), ...(style.objectFit ? { objectFit: style.objectFit } : {}) } : imgBaseStyle;
|
|
10333
10351
|
if (enableZoom) {
|
|
10334
10352
|
return (React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-hover', className), style: { position: 'relative', ...style }, onMouseEnter: () => setHovering(true), onMouseLeave: () => setHovering(false) },
|
|
10335
10353
|
React.createElement(ImageZoom, { src: src, alt: alt, mode: zoomMode, zoomLevel: zoomLevel, images: safeImages, currentIndex: showSecond ? 1 : 0, className: "seekora-img-hover-img", style: hoverImgStyle })));
|
|
@@ -10348,7 +10366,7 @@ function ImageDisplay({ images, variant = 'single', alt = '', className, style,
|
|
|
10348
10366
|
return next;
|
|
10349
10367
|
});
|
|
10350
10368
|
};
|
|
10351
|
-
const carouselImgStyle = style?.aspectRatio ? { ...imgBaseStyle, aspectRatio: style.aspectRatio } : imgBaseStyle;
|
|
10369
|
+
const carouselImgStyle = style?.aspectRatio || style?.objectFit ? { ...imgBaseStyle, ...(style.aspectRatio ? { aspectRatio: style.aspectRatio } : {}), ...(style.objectFit ? { objectFit: style.objectFit } : {}) } : imgBaseStyle;
|
|
10352
10370
|
const mainImage = enableZoom ? (React.createElement(ImageZoom, { src: current, alt: alt, mode: zoomMode, zoomLevel: zoomLevel, images: safeImages, currentIndex: index, className: "seekora-img-carousel-main", style: carouselImgStyle })) : (React.createElement("img", { src: current, alt: alt, className: "seekora-img-carousel-main", style: carouselImgStyle, loading: "lazy" }));
|
|
10353
10371
|
return (React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-carousel', className), style: { position: 'relative', ...style } },
|
|
10354
10372
|
mainImage,
|
|
@@ -10380,7 +10398,7 @@ function ImageDisplay({ images, variant = 'single', alt = '', className, style,
|
|
|
10380
10398
|
} })))))))));
|
|
10381
10399
|
}
|
|
10382
10400
|
if (variant === 'thumbStrip' || variant === 'thumbList') {
|
|
10383
|
-
const thumbMainStyle = style?.aspectRatio ? { ...imgBaseStyle, aspectRatio: style.aspectRatio } : imgBaseStyle;
|
|
10401
|
+
const thumbMainStyle = style?.aspectRatio || style?.objectFit ? { ...imgBaseStyle, ...(style.aspectRatio ? { aspectRatio: style.aspectRatio } : {}), ...(style.objectFit ? { objectFit: style.objectFit } : {}) } : imgBaseStyle;
|
|
10384
10402
|
const mainImage = enableZoom ? (React.createElement(ImageZoom, { src: current, alt: alt, mode: zoomMode, zoomLevel: zoomLevel, images: safeImages, currentIndex: index, className: "seekora-img-thumb-main", style: thumbMainStyle })) : (React.createElement("img", { src: current, alt: alt, className: "seekora-img-thumb-main", style: thumbMainStyle, loading: "lazy" }));
|
|
10385
10403
|
return (React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-thumbstrip', className), style: { display: 'flex', flexDirection: 'column', gap: responsiveGap, ...style } },
|
|
10386
10404
|
mainImage,
|
|
@@ -11563,15 +11581,15 @@ const imgPlaceholderStyle = {
|
|
|
11563
11581
|
borderRadius: BORDER_RADIUS$1.sm,
|
|
11564
11582
|
backgroundColor: 'var(--seekora-bg-secondary, rgba(0,0,0,0.04))',
|
|
11565
11583
|
};
|
|
11566
|
-
function ImageBlock({ images, title, imageVariant, aspectRatio, enableZoom, zoomMode, zoomLevel }) {
|
|
11584
|
+
function ImageBlock({ images, title, imageVariant, aspectRatio, objectFit, enableZoom, zoomMode, zoomLevel }) {
|
|
11567
11585
|
const ar = aspectRatio
|
|
11568
11586
|
? (aspectRatio.includes(':') ? aspectRatio.replace(':', '/') : aspectRatio)
|
|
11569
11587
|
: '1';
|
|
11570
11588
|
if (images.length > 0) {
|
|
11571
11589
|
return (React.createElement("div", { className: "seekora-product-card__image", style: { position: 'relative', overflow: 'hidden', borderRadius: BORDER_RADIUS$1.sm } },
|
|
11572
|
-
React.createElement(ImageDisplay, { images: images, variant: images.length > 1 ? imageVariant : 'single', alt: title, className: "seekora-suggestions-product-card-image", style: { aspectRatio: ar }, enableZoom: enableZoom, zoomMode: zoomMode, zoomLevel: zoomLevel })));
|
|
11590
|
+
React.createElement(ImageDisplay, { images: images, variant: images.length > 1 ? imageVariant : 'single', alt: title, className: "seekora-suggestions-product-card-image", style: { aspectRatio: ar, ...(objectFit ? { objectFit } : {}) }, enableZoom: enableZoom, zoomMode: zoomMode, zoomLevel: zoomLevel })));
|
|
11573
11591
|
}
|
|
11574
|
-
return React.createElement("div", { className: "seekora-product-card__image seekora-suggestions-product-card-placeholder", style: { ...imgPlaceholderStyle, aspectRatio: ar }, "aria-hidden": true });
|
|
11592
|
+
return React.createElement("div", { className: "seekora-product-card__image seekora-suggestions-product-card-placeholder", style: { ...imgPlaceholderStyle, aspectRatio: ar, ...(objectFit ? { objectFit } : {}) }, "aria-hidden": true });
|
|
11575
11593
|
}
|
|
11576
11594
|
/** minimal: image, title, price (current default behavior) */
|
|
11577
11595
|
function MinimalLayout({ images, title, price, product, imageVariant, displayConfig, enableImageZoom, imageZoomMode, imageZoomLevel }) {
|
|
@@ -11579,7 +11597,7 @@ function MinimalLayout({ images, title, price, product, imageVariant, displayCon
|
|
|
11579
11597
|
const titleFontSize = isMobile ? '0.9375rem' : '0.875rem'; // Slightly larger on mobile
|
|
11580
11598
|
const priceFontSize = isMobile ? '0.9375rem' : '0.875rem';
|
|
11581
11599
|
return (React.createElement(React.Fragment, null,
|
|
11582
|
-
React.createElement(ImageBlock, { images: images, title: title, imageVariant: imageVariant, aspectRatio: displayConfig.imageAspectRatio, enableZoom: enableImageZoom, zoomMode: imageZoomMode, zoomLevel: imageZoomLevel }),
|
|
11600
|
+
React.createElement(ImageBlock, { images: images, title: title, imageVariant: imageVariant, aspectRatio: displayConfig.imageAspectRatio, objectFit: displayConfig.imageObjectFit, enableZoom: enableImageZoom, zoomMode: imageZoomMode, zoomLevel: imageZoomLevel }),
|
|
11583
11601
|
React.createElement("span", { className: "seekora-product-card__title", style: { fontSize: titleFontSize, fontWeight: 500, lineHeight: 1.4, overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' } }, title),
|
|
11584
11602
|
price != null && !Number.isNaN(price) && (React.createElement("span", { className: "seekora-product-card__price seekora-suggestions-product-card-price", style: { fontSize: priceFontSize, color: 'inherit', opacity: 0.6 } },
|
|
11585
11603
|
product.currency ?? '$',
|
|
@@ -11597,7 +11615,7 @@ function StandardLayout({ images, title, price, comparePrice, brand, badges, opt
|
|
|
11597
11615
|
const isOverlayPosition = actionButtonsPosition?.startsWith('overlay');
|
|
11598
11616
|
return (React.createElement(React.Fragment, null,
|
|
11599
11617
|
React.createElement("div", { style: { position: 'relative' } },
|
|
11600
|
-
React.createElement(ImageBlock, { images: images, title: title, imageVariant: imageVariant, aspectRatio: cfg.imageAspectRatio, enableZoom: enableImageZoom, zoomMode: imageZoomMode, zoomLevel: imageZoomLevel }),
|
|
11618
|
+
React.createElement(ImageBlock, { images: images, title: title, imageVariant: imageVariant, aspectRatio: cfg.imageAspectRatio, objectFit: cfg.imageObjectFit, enableZoom: enableImageZoom, zoomMode: imageZoomMode, zoomLevel: imageZoomLevel }),
|
|
11601
11619
|
cfg.showBadges !== false && badges.length > 0 && (React.createElement(BadgeList, { badges: badges, position: "top-left", maxBadges: 2 })),
|
|
11602
11620
|
actionButtons && actionButtons.length > 0 && isOverlayPosition && (React.createElement(ActionButtons, { buttons: actionButtons, position: actionButtonsPosition === 'overlay-top-right' ? 'top-right' : 'bottom-center', showLabels: showActionLabels, size: "small" }))),
|
|
11603
11621
|
React.createElement("div", { className: "seekora-product-card__body", style: { display: 'flex', flexDirection: 'column', gap: bodyGap } },
|
|
@@ -11616,7 +11634,7 @@ function DetailedLayout({ images, title, price, comparePrice, brand, badges, pri
|
|
|
11616
11634
|
const isOverlayPosition = actionButtonsPosition?.startsWith('overlay');
|
|
11617
11635
|
return (React.createElement(React.Fragment, null,
|
|
11618
11636
|
React.createElement("div", { style: { position: 'relative' } },
|
|
11619
|
-
React.createElement(ImageBlock, { images: images, title: title, imageVariant: imageVariant, aspectRatio: cfg.imageAspectRatio, enableZoom: enableImageZoom, zoomMode: imageZoomMode, zoomLevel: imageZoomLevel }),
|
|
11637
|
+
React.createElement(ImageBlock, { images: images, title: title, imageVariant: imageVariant, aspectRatio: cfg.imageAspectRatio, objectFit: cfg.imageObjectFit, enableZoom: enableImageZoom, zoomMode: imageZoomMode, zoomLevel: imageZoomLevel }),
|
|
11620
11638
|
cfg.showBadges !== false && badges.length > 0 && (React.createElement(BadgeList, { badges: badges, position: "top-left" })),
|
|
11621
11639
|
actionButtons && actionButtons.length > 0 && isOverlayPosition && (React.createElement(ActionButtons, { buttons: actionButtons, position: actionButtonsPosition === 'overlay-top-right' ? 'top-right' : 'bottom-center', showLabels: showActionLabels, size: "small" }))),
|
|
11622
11640
|
React.createElement("div", { className: "seekora-product-card__body", style: { display: 'flex', flexDirection: 'column', gap: 4 } },
|