@seekora-ai/ui-sdk-react 0.2.22 → 0.2.24
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 +4 -0
- package/dist/components/Facets.d.ts.map +1 -1
- package/dist/components/Facets.js +12 -5
- package/dist/components/HitsPerPage.d.ts.map +1 -1
- package/dist/components/HitsPerPage.js +21 -11
- package/dist/components/SortBy.d.ts.map +1 -1
- package/dist/components/SortBy.js +8 -11
- package/dist/components/primitives/CustomSelect.d.ts +40 -0
- package/dist/components/primitives/CustomSelect.d.ts.map +1 -0
- package/dist/components/primitives/CustomSelect.js +196 -0
- package/dist/components/primitives/ImageZoom.d.ts.map +1 -1
- package/dist/components/primitives/ImageZoom.js +37 -16
- package/dist/components/primitives/VariantSelector.d.ts.map +1 -1
- package/dist/components/primitives/VariantSelector.js +17 -24
- package/dist/components/primitives/index.d.ts +1 -0
- package/dist/components/primitives/index.d.ts.map +1 -1
- package/dist/components/primitives/index.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/src/index.d.ts +46 -2
- package/dist/src/index.esm.js +313 -82
- package/dist/src/index.esm.js.map +1 -1
- package/dist/src/index.js +313 -81
- package/dist/src/index.js.map +1 -1
- package/package.json +3 -3
package/dist/src/index.js
CHANGED
|
@@ -303,6 +303,7 @@ class SearchStateManager {
|
|
|
303
303
|
constructor(config) {
|
|
304
304
|
this.listeners = [];
|
|
305
305
|
this.debounceTimer = null;
|
|
306
|
+
this.notifyScheduled = false;
|
|
306
307
|
this.client = config.client;
|
|
307
308
|
this.autoSearch = config.autoSearch !== false;
|
|
308
309
|
this.debounceMs = config.debounceMs || 300;
|
|
@@ -550,19 +551,27 @@ class SearchStateManager {
|
|
|
550
551
|
this.state = { ...this.state, ...updates };
|
|
551
552
|
this.notifyListeners();
|
|
552
553
|
}
|
|
553
|
-
// Notify all listeners of state changes
|
|
554
|
+
// Notify all listeners of state changes, batched via microtask.
|
|
555
|
+
// Multiple synchronous mutations (e.g. addRefinement + page reset)
|
|
556
|
+
// coalesce into a single listener notification.
|
|
554
557
|
notifyListeners() {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
|
|
558
|
+
if (this.notifyScheduled)
|
|
559
|
+
return;
|
|
560
|
+
this.notifyScheduled = true;
|
|
561
|
+
queueMicrotask(() => {
|
|
562
|
+
this.notifyScheduled = false;
|
|
563
|
+
const state = this.getState();
|
|
564
|
+
this.listeners.forEach(listener => {
|
|
565
|
+
try {
|
|
566
|
+
listener(state);
|
|
567
|
+
}
|
|
568
|
+
catch (err) {
|
|
569
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
570
|
+
log.error('SearchStateManager: Error in listener', {
|
|
571
|
+
error: error.message,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
});
|
|
566
575
|
});
|
|
567
576
|
}
|
|
568
577
|
/** Explicitly clear results (bypasses keepResultsOnClear) */
|
|
@@ -608,10 +617,13 @@ class SearchStateManager {
|
|
|
608
617
|
async fetchFilters(options) {
|
|
609
618
|
log.verbose('SearchStateManager: Fetching filters', { options });
|
|
610
619
|
try {
|
|
611
|
-
|
|
620
|
+
// Do NOT pass refinement-based filters to the Filters API.
|
|
621
|
+
// Facets should be generated from the search query only, not narrowed
|
|
622
|
+
// by active filter selections. This keeps facet options stable when
|
|
623
|
+
// users toggle filters (same behaviour as performSimplifiedFacetSearch
|
|
624
|
+
// in the search API).
|
|
612
625
|
const response = await this.client.getFilters({
|
|
613
626
|
q: this.state.query || undefined,
|
|
614
|
-
filter: filterString || undefined,
|
|
615
627
|
...options,
|
|
616
628
|
});
|
|
617
629
|
log.info('SearchStateManager: Filters fetched', {
|
|
@@ -3114,6 +3126,201 @@ const Pagination = ({ results: resultsProp, currentPage: currentPageProp, itemsP
|
|
|
3114
3126
|
resolvedShowPageInfo && (React.createElement("li", { style: { marginLeft: theme.spacing.small } }, pageInfoElement)))));
|
|
3115
3127
|
};
|
|
3116
3128
|
|
|
3129
|
+
/**
|
|
3130
|
+
* CustomSelect — lightweight, accessible custom dropdown replacement for native <select>.
|
|
3131
|
+
*
|
|
3132
|
+
* CSS Variables (apply on a parent element to customize):
|
|
3133
|
+
* --seekora-select-bg — background color (default: #fff)
|
|
3134
|
+
* --seekora-select-color — text color (default: inherit)
|
|
3135
|
+
* --seekora-select-border — border color (default: rgba(128,128,128,0.3))
|
|
3136
|
+
* --seekora-select-hover-bg — option hover background (default: #f3f4f6)
|
|
3137
|
+
* --seekora-select-active-bg — selected option background (default: #eff6ff)
|
|
3138
|
+
* --seekora-select-active-color — selected option text color (default: inherit)
|
|
3139
|
+
* --seekora-select-radius — border radius (default: 6px)
|
|
3140
|
+
* --seekora-select-font-size — font size (default: 0.875rem)
|
|
3141
|
+
*/
|
|
3142
|
+
// ---------------------------------------------------------------------------
|
|
3143
|
+
// Component
|
|
3144
|
+
// ---------------------------------------------------------------------------
|
|
3145
|
+
const CustomSelect = ({ value, onChange, options, placeholder = 'Select...', className, style, theme: customTheme, 'aria-label': ariaLabel, disabled = false, }) => {
|
|
3146
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
3147
|
+
const [activeIndex, setActiveIndex] = React.useState(-1);
|
|
3148
|
+
const containerRef = React.useRef(null);
|
|
3149
|
+
const menuRef = React.useRef(null);
|
|
3150
|
+
const instanceId = React.useId();
|
|
3151
|
+
const t = customTheme || {};
|
|
3152
|
+
const selectedOption = options.find((o) => o.value === value);
|
|
3153
|
+
const displayLabel = selectedOption?.label ?? placeholder;
|
|
3154
|
+
// Close on click outside
|
|
3155
|
+
React.useEffect(() => {
|
|
3156
|
+
if (!isOpen)
|
|
3157
|
+
return;
|
|
3158
|
+
const handleMouseDown = (e) => {
|
|
3159
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
3160
|
+
setIsOpen(false);
|
|
3161
|
+
setActiveIndex(-1);
|
|
3162
|
+
}
|
|
3163
|
+
};
|
|
3164
|
+
document.addEventListener('mousedown', handleMouseDown);
|
|
3165
|
+
return () => document.removeEventListener('mousedown', handleMouseDown);
|
|
3166
|
+
}, [isOpen]);
|
|
3167
|
+
// Scroll active item into view
|
|
3168
|
+
React.useEffect(() => {
|
|
3169
|
+
if (!isOpen || activeIndex < 0 || !menuRef.current)
|
|
3170
|
+
return;
|
|
3171
|
+
const item = menuRef.current.children[activeIndex];
|
|
3172
|
+
item?.scrollIntoView?.({ block: 'nearest' });
|
|
3173
|
+
}, [activeIndex, isOpen]);
|
|
3174
|
+
const toggle = React.useCallback(() => {
|
|
3175
|
+
if (disabled)
|
|
3176
|
+
return;
|
|
3177
|
+
setIsOpen((prev) => {
|
|
3178
|
+
if (!prev) {
|
|
3179
|
+
// Opening — highlight current selection
|
|
3180
|
+
const idx = options.findIndex((o) => o.value === value);
|
|
3181
|
+
setActiveIndex(idx >= 0 ? idx : 0);
|
|
3182
|
+
}
|
|
3183
|
+
return !prev;
|
|
3184
|
+
});
|
|
3185
|
+
}, [disabled, options, value]);
|
|
3186
|
+
const selectOption = React.useCallback((opt) => {
|
|
3187
|
+
if (opt.disabled)
|
|
3188
|
+
return;
|
|
3189
|
+
onChange(opt.value);
|
|
3190
|
+
setIsOpen(false);
|
|
3191
|
+
setActiveIndex(-1);
|
|
3192
|
+
}, [onChange]);
|
|
3193
|
+
// Find next non-disabled index in given direction
|
|
3194
|
+
const findNextEnabled = (from, dir) => {
|
|
3195
|
+
let idx = from;
|
|
3196
|
+
for (let i = 0; i < options.length; i++) {
|
|
3197
|
+
idx += dir;
|
|
3198
|
+
if (idx < 0)
|
|
3199
|
+
idx = options.length - 1;
|
|
3200
|
+
if (idx >= options.length)
|
|
3201
|
+
idx = 0;
|
|
3202
|
+
if (!options[idx].disabled)
|
|
3203
|
+
return idx;
|
|
3204
|
+
}
|
|
3205
|
+
return from;
|
|
3206
|
+
};
|
|
3207
|
+
const handleKeyDown = React.useCallback((e) => {
|
|
3208
|
+
if (disabled)
|
|
3209
|
+
return;
|
|
3210
|
+
if (!isOpen) {
|
|
3211
|
+
if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
|
|
3212
|
+
e.preventDefault();
|
|
3213
|
+
toggle();
|
|
3214
|
+
}
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
switch (e.key) {
|
|
3218
|
+
case 'ArrowDown':
|
|
3219
|
+
e.preventDefault();
|
|
3220
|
+
setActiveIndex((prev) => findNextEnabled(prev, 1));
|
|
3221
|
+
break;
|
|
3222
|
+
case 'ArrowUp':
|
|
3223
|
+
e.preventDefault();
|
|
3224
|
+
setActiveIndex((prev) => findNextEnabled(prev, -1));
|
|
3225
|
+
break;
|
|
3226
|
+
case 'Enter':
|
|
3227
|
+
case ' ':
|
|
3228
|
+
e.preventDefault();
|
|
3229
|
+
if (activeIndex >= 0 && !options[activeIndex].disabled) {
|
|
3230
|
+
selectOption(options[activeIndex]);
|
|
3231
|
+
}
|
|
3232
|
+
break;
|
|
3233
|
+
case 'Escape':
|
|
3234
|
+
e.preventDefault();
|
|
3235
|
+
setIsOpen(false);
|
|
3236
|
+
setActiveIndex(-1);
|
|
3237
|
+
break;
|
|
3238
|
+
case 'Home':
|
|
3239
|
+
e.preventDefault();
|
|
3240
|
+
setActiveIndex(findNextEnabled(-1, 1));
|
|
3241
|
+
break;
|
|
3242
|
+
case 'End':
|
|
3243
|
+
e.preventDefault();
|
|
3244
|
+
setActiveIndex(findNextEnabled(options.length, -1));
|
|
3245
|
+
break;
|
|
3246
|
+
case 'Tab':
|
|
3247
|
+
setIsOpen(false);
|
|
3248
|
+
setActiveIndex(-1);
|
|
3249
|
+
break;
|
|
3250
|
+
}
|
|
3251
|
+
}, [disabled, isOpen, activeIndex, options, toggle, selectOption]);
|
|
3252
|
+
const menuId = `seekora-select-menu-${instanceId}`;
|
|
3253
|
+
return (React.createElement("div", { ref: containerRef, className: clsx('seekora-select', className), style: { position: 'relative', display: 'inline-block', ...style }, onKeyDown: handleKeyDown },
|
|
3254
|
+
React.createElement("button", { type: "button", role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": menuId, "aria-label": ariaLabel, "aria-activedescendant": isOpen && activeIndex >= 0
|
|
3255
|
+
? `${menuId}-opt-${activeIndex}`
|
|
3256
|
+
: undefined, disabled: disabled, onClick: toggle, className: clsx('seekora-select__trigger', t.trigger), style: {
|
|
3257
|
+
display: 'flex',
|
|
3258
|
+
alignItems: 'center',
|
|
3259
|
+
justifyContent: 'space-between',
|
|
3260
|
+
gap: 8,
|
|
3261
|
+
width: '100%',
|
|
3262
|
+
padding: '8px 12px',
|
|
3263
|
+
fontSize: 'var(--seekora-select-font-size, 0.875rem)',
|
|
3264
|
+
lineHeight: 1.4,
|
|
3265
|
+
border: '1px solid var(--seekora-select-border, rgba(128,128,128,0.3))',
|
|
3266
|
+
borderRadius: 'var(--seekora-select-radius, 6px)',
|
|
3267
|
+
backgroundColor: 'var(--seekora-select-bg, #fff)',
|
|
3268
|
+
color: 'var(--seekora-select-color, inherit)',
|
|
3269
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
3270
|
+
outline: 'none',
|
|
3271
|
+
textAlign: 'left',
|
|
3272
|
+
fontFamily: 'inherit',
|
|
3273
|
+
opacity: disabled ? 0.5 : 1,
|
|
3274
|
+
} },
|
|
3275
|
+
React.createElement("span", { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, displayLabel),
|
|
3276
|
+
React.createElement("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", style: {
|
|
3277
|
+
flexShrink: 0,
|
|
3278
|
+
transition: 'transform 0.15s ease',
|
|
3279
|
+
transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
|
|
3280
|
+
} },
|
|
3281
|
+
React.createElement("path", { d: "M2.5 4.5L6 8L9.5 4.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }))),
|
|
3282
|
+
isOpen && (React.createElement("div", { ref: menuRef, id: menuId, role: "listbox", className: clsx('seekora-select__menu', t.menu), style: {
|
|
3283
|
+
position: 'absolute',
|
|
3284
|
+
top: '100%',
|
|
3285
|
+
left: 0,
|
|
3286
|
+
right: 0,
|
|
3287
|
+
zIndex: 9999,
|
|
3288
|
+
marginTop: 4,
|
|
3289
|
+
padding: '4px 0',
|
|
3290
|
+
border: '1px solid var(--seekora-select-border, rgba(128,128,128,0.3))',
|
|
3291
|
+
borderRadius: 'var(--seekora-select-radius, 6px)',
|
|
3292
|
+
backgroundColor: 'var(--seekora-select-bg, #fff)',
|
|
3293
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
|
3294
|
+
maxHeight: 220,
|
|
3295
|
+
overflowY: 'auto',
|
|
3296
|
+
} }, options.map((opt, idx) => {
|
|
3297
|
+
const isSelected = opt.value === value;
|
|
3298
|
+
const isHighlighted = idx === activeIndex;
|
|
3299
|
+
return (React.createElement("div", { key: opt.value, id: `${menuId}-opt-${idx}`, role: "option", "aria-selected": isSelected, "aria-disabled": opt.disabled || undefined, className: clsx('seekora-select__option', t.option, isSelected && 'seekora-select__option--selected', isHighlighted && (t.optionActive || 'seekora-select__option--highlighted'), opt.disabled && (t.optionDisabled || 'seekora-select__option--disabled')), onMouseEnter: () => !opt.disabled && setActiveIndex(idx), onMouseDown: (e) => {
|
|
3300
|
+
e.preventDefault(); // Prevent blur
|
|
3301
|
+
if (!opt.disabled)
|
|
3302
|
+
selectOption(opt);
|
|
3303
|
+
}, style: {
|
|
3304
|
+
padding: '6px 12px',
|
|
3305
|
+
fontSize: 'var(--seekora-select-font-size, 0.875rem)',
|
|
3306
|
+
cursor: opt.disabled ? 'not-allowed' : 'pointer',
|
|
3307
|
+
backgroundColor: isHighlighted
|
|
3308
|
+
? 'var(--seekora-select-hover-bg, #f3f4f6)'
|
|
3309
|
+
: isSelected
|
|
3310
|
+
? 'var(--seekora-select-active-bg, #eff6ff)'
|
|
3311
|
+
: 'transparent',
|
|
3312
|
+
color: opt.disabled
|
|
3313
|
+
? 'rgba(128,128,128,0.5)'
|
|
3314
|
+
: isSelected
|
|
3315
|
+
? 'var(--seekora-select-active-color, inherit)'
|
|
3316
|
+
: 'var(--seekora-select-color, inherit)',
|
|
3317
|
+
fontWeight: isSelected ? 500 : 400,
|
|
3318
|
+
opacity: opt.disabled ? 0.5 : 1,
|
|
3319
|
+
transition: 'background-color 0.1s ease',
|
|
3320
|
+
} }, opt.label));
|
|
3321
|
+
})))));
|
|
3322
|
+
};
|
|
3323
|
+
|
|
3117
3324
|
/**
|
|
3118
3325
|
* SortBy Component
|
|
3119
3326
|
*
|
|
@@ -3217,18 +3424,14 @@ const SortBy = ({ options, value: valueProp, defaultValue, onSortChange, renderS
|
|
|
3217
3424
|
}
|
|
3218
3425
|
return (React.createElement("div", { className: clsx(sortByTheme.container, className), style: { ...cssVarStyle, ...style } },
|
|
3219
3426
|
labelElement,
|
|
3220
|
-
React.createElement(
|
|
3221
|
-
padding,
|
|
3222
|
-
paddingRight: theme.spacing.medium,
|
|
3223
|
-
fontSize,
|
|
3224
|
-
border: '1px solid var(--seekora-sort-border)',
|
|
3225
|
-
borderRadius,
|
|
3226
|
-
backgroundColor: 'var(--seekora-sort-bg)',
|
|
3227
|
-
color: 'var(--seekora-sort-color)',
|
|
3228
|
-
cursor: 'pointer',
|
|
3229
|
-
outline: 'none',
|
|
3427
|
+
React.createElement(CustomSelect, { value: value, onChange: applyValue, options: options, placeholder: placeholder, "aria-label": label || 'Sort results', className: clsx(sortByTheme.select), style: {
|
|
3230
3428
|
width: '100%',
|
|
3231
|
-
|
|
3429
|
+
'--seekora-select-font-size': fontSize,
|
|
3430
|
+
'--seekora-select-border': 'var(--seekora-sort-border)',
|
|
3431
|
+
'--seekora-select-radius': borderRadius,
|
|
3432
|
+
'--seekora-select-bg': 'var(--seekora-sort-bg)',
|
|
3433
|
+
'--seekora-select-color': 'var(--seekora-sort-color)',
|
|
3434
|
+
} })));
|
|
3232
3435
|
}
|
|
3233
3436
|
// ------ Button group variant -------------------------------------------
|
|
3234
3437
|
if (variant === 'button-group') {
|
|
@@ -3678,7 +3881,7 @@ const CSS_VAR_DEFAULTS = {
|
|
|
3678
3881
|
// ---------------------------------------------------------------------------
|
|
3679
3882
|
// Component
|
|
3680
3883
|
// ---------------------------------------------------------------------------
|
|
3681
|
-
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, }) => {
|
|
3884
|
+
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, }) => {
|
|
3682
3885
|
const { theme } = useSearchContext();
|
|
3683
3886
|
const { results: stateResults, refinements, addRefinement, removeRefinement } = useSearchState();
|
|
3684
3887
|
const facetsTheme = customTheme || {};
|
|
@@ -3755,7 +3958,10 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
|
|
|
3755
3958
|
});
|
|
3756
3959
|
return extracted;
|
|
3757
3960
|
};
|
|
3758
|
-
const
|
|
3961
|
+
const rawFacetList = extractFacets();
|
|
3962
|
+
const facets = hideEmptyFacets
|
|
3963
|
+
? rawFacetList.filter(f => f.items.length > 0 || f.stats != null)
|
|
3964
|
+
: rawFacetList;
|
|
3759
3965
|
// -------------------------------------------------------------------
|
|
3760
3966
|
// Handlers
|
|
3761
3967
|
// -------------------------------------------------------------------
|
|
@@ -3792,17 +3998,21 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
|
|
|
3792
3998
|
}));
|
|
3793
3999
|
};
|
|
3794
4000
|
/** For collapsible variant — determine if a facet group is open. */
|
|
4001
|
+
const isFieldDefaultCollapsed = (field) => {
|
|
4002
|
+
if (defaultCollapsedFields?.includes(field))
|
|
4003
|
+
return true;
|
|
4004
|
+
return defaultCollapsed;
|
|
4005
|
+
};
|
|
3795
4006
|
const isFacetGroupOpen = (field) => {
|
|
3796
4007
|
if (field in expandedFacets) {
|
|
3797
4008
|
return expandedFacets[field];
|
|
3798
4009
|
}
|
|
3799
|
-
|
|
3800
|
-
return !defaultCollapsed;
|
|
4010
|
+
return !isFieldDefaultCollapsed(field);
|
|
3801
4011
|
};
|
|
3802
4012
|
const toggleCollapsible = (field) => {
|
|
3803
4013
|
setExpandedFacets((prev) => ({
|
|
3804
4014
|
...prev,
|
|
3805
|
-
[field]: !(prev[field] ?? !
|
|
4015
|
+
[field]: !(prev[field] ?? !isFieldDefaultCollapsed(field)),
|
|
3806
4016
|
}));
|
|
3807
4017
|
};
|
|
3808
4018
|
const getSearchTerm = (field) => searchTerms[field] || '';
|
|
@@ -5439,19 +5649,28 @@ const HitsPerPage = ({ items, onHitsPerPageChange, renderSelect, className, styl
|
|
|
5439
5649
|
fontSize: theme.typography.fontSize.medium,
|
|
5440
5650
|
color: theme.colors.text,
|
|
5441
5651
|
} }, label)),
|
|
5442
|
-
React.createElement(
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5652
|
+
React.createElement(CustomSelect, { value: String(value), onChange: (val) => {
|
|
5653
|
+
const newValue = parseInt(val, 10);
|
|
5654
|
+
setInternalValue(newValue);
|
|
5655
|
+
if (syncWithState) {
|
|
5656
|
+
stateManager.setItemsPerPage(newValue, false);
|
|
5657
|
+
setPage(1, true);
|
|
5658
|
+
}
|
|
5659
|
+
if (onHitsPerPageChange) {
|
|
5660
|
+
onHitsPerPageChange(newValue);
|
|
5661
|
+
}
|
|
5662
|
+
}, options: items.map((item) => ({
|
|
5663
|
+
value: String(item.value),
|
|
5664
|
+
label: item.label,
|
|
5665
|
+
})), "aria-label": "Results per page", className: hitsPerPageTheme.select, style: {
|
|
5666
|
+
'--seekora-select-font-size': theme.typography.fontSize.medium,
|
|
5667
|
+
'--seekora-select-border': theme.colors.border,
|
|
5668
|
+
'--seekora-select-radius': typeof theme.borderRadius === 'string'
|
|
5448
5669
|
? theme.borderRadius
|
|
5449
5670
|
: theme.borderRadius.medium,
|
|
5450
|
-
|
|
5451
|
-
color: theme.colors.text,
|
|
5452
|
-
|
|
5453
|
-
outline: 'none',
|
|
5454
|
-
}, "aria-label": "Results per page" }, items.map((item) => (React.createElement("option", { key: item.value, value: item.value, className: hitsPerPageTheme.option }, item.label))))));
|
|
5671
|
+
'--seekora-select-bg': theme.colors.background,
|
|
5672
|
+
'--seekora-select-color': theme.colors.text,
|
|
5673
|
+
} })));
|
|
5455
5674
|
};
|
|
5456
5675
|
|
|
5457
5676
|
/**
|
|
@@ -9720,13 +9939,30 @@ function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, className, s
|
|
|
9720
9939
|
objectFit: 'cover',
|
|
9721
9940
|
display: 'block',
|
|
9722
9941
|
};
|
|
9942
|
+
// Compute the indicator rectangle (what portion of the image the zoom panel shows).
|
|
9943
|
+
// indW / containerW = 1 / zoomLevel (the panel shows 1/zoomLevel of the image width).
|
|
9944
|
+
// indH is derived from the zoom panel's aspect ratio so it's consistent with what the
|
|
9945
|
+
// panel actually renders — no need for the image's natural dimensions.
|
|
9946
|
+
const getIndicatorRect = React.useCallback(() => {
|
|
9947
|
+
if (!containerRef.current)
|
|
9948
|
+
return { indL: 0, indT: 0, indW: 0, indH: 0 };
|
|
9949
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
9950
|
+
const indW = rect.width / zoomLevel;
|
|
9951
|
+
const indH = (zoomPanelSize.height / zoomPanelSize.width) * rect.width / zoomLevel;
|
|
9952
|
+
const indL = Math.max(0, Math.min(rect.width - indW, cursorPos.x - indW / 2));
|
|
9953
|
+
const indT = Math.max(0, Math.min(rect.height - indH, cursorPos.y - indH / 2));
|
|
9954
|
+
return { indL, indT, indW, indH };
|
|
9955
|
+
}, [cursorPos, zoomLevel, zoomPanelSize]);
|
|
9723
9956
|
// Calculate zoom panel background position (Amazon-style)
|
|
9724
9957
|
const getZoomPanelStyle = () => {
|
|
9725
9958
|
if (!containerRef.current || !imageLoaded)
|
|
9726
9959
|
return { display: 'none' };
|
|
9727
9960
|
const rect = containerRef.current.getBoundingClientRect();
|
|
9728
|
-
const
|
|
9729
|
-
|
|
9961
|
+
const { indL, indT, indW, indH } = getIndicatorRect();
|
|
9962
|
+
// bgPos is derived from the clamped indicator position, not the raw cursor.
|
|
9963
|
+
// At indL=0 → bgPosX=0% (show left), at indL=containerW-indW → bgPosX=100% (show right).
|
|
9964
|
+
const bgPosX = (rect.width - indW) > 0 ? (indL / (rect.width - indW)) * 100 : 0;
|
|
9965
|
+
const bgPosY = (rect.height - indH) > 0 ? (indT / (rect.height - indH)) * 100 : 0;
|
|
9730
9966
|
// Calculate available space in all directions
|
|
9731
9967
|
const viewportWidth = window.innerWidth;
|
|
9732
9968
|
const viewportHeight = window.innerHeight;
|
|
@@ -9854,19 +10090,22 @@ function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, className, s
|
|
|
9854
10090
|
} }, "\uD83D\uDD0D")),
|
|
9855
10091
|
supportsLens && isHovering && imageLoaded && (React.createElement("div", { style: getLensStyle() },
|
|
9856
10092
|
React.createElement("img", { src: src, alt: "", style: getLensImageStyle() }))),
|
|
9857
|
-
supportsHover && isHovering && imageLoaded && containerRef.current && (
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
10093
|
+
supportsHover && isHovering && imageLoaded && containerRef.current && (() => {
|
|
10094
|
+
const { indL, indT, indW, indH } = getIndicatorRect();
|
|
10095
|
+
return (React.createElement("div", { style: {
|
|
10096
|
+
position: 'absolute',
|
|
10097
|
+
left: indL,
|
|
10098
|
+
top: indT,
|
|
10099
|
+
width: indW,
|
|
10100
|
+
height: indH,
|
|
10101
|
+
border: 'var(--seekora-hover-area-border, 2px solid rgba(0,0,0,0.3))',
|
|
10102
|
+
backgroundColor: 'var(--seekora-hover-area-bg, rgba(255,255,255,0.1))',
|
|
10103
|
+
pointerEvents: 'none',
|
|
10104
|
+
zIndex: 50,
|
|
10105
|
+
} }));
|
|
10106
|
+
})()),
|
|
10107
|
+
supportsHover && isHovering && imageLoaded && typeof document !== 'undefined' && reactDom.createPortal(React.createElement("div", { style: getZoomPanelStyle() }), document.body),
|
|
10108
|
+
isLightboxOpen && typeof document !== 'undefined' && reactDom.createPortal(React.createElement("div", { className: "seekora-image-zoom-lightbox", style: {
|
|
9870
10109
|
position: 'fixed',
|
|
9871
10110
|
top: 0,
|
|
9872
10111
|
left: 0,
|
|
@@ -10021,7 +10260,7 @@ function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, className, s
|
|
|
10021
10260
|
backgroundColor: 'var(--seekora-lightbox-instructions-bg, rgba(0,0,0,0.5))',
|
|
10022
10261
|
padding: '8px 16px',
|
|
10023
10262
|
borderRadius: 12,
|
|
10024
|
-
} }, hasMultipleImages ? 'Use arrow keys or click thumbnails to navigate • ESC to close' : 'Click outside or press ESC to close')))));
|
|
10263
|
+
} }, hasMultipleImages ? 'Use arrow keys or click thumbnails to navigate • ESC to close' : 'Click outside or press ESC to close')), document.body)));
|
|
10025
10264
|
}
|
|
10026
10265
|
|
|
10027
10266
|
/**
|
|
@@ -11852,30 +12091,22 @@ function VariantSelector({ options, variants, selections, onSelectionChange, opt
|
|
|
11852
12091
|
} },
|
|
11853
12092
|
option.name,
|
|
11854
12093
|
selected && (React.createElement("span", { style: { fontWeight: 400, marginLeft: 6, color: 'var(--seekora-text-secondary, inherit)' } }, selected))),
|
|
11855
|
-
mode === 'dropdown' ? (React.createElement("
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
|
|
11869
|
-
|
|
11870
|
-
option.
|
|
11871
|
-
option.values.map((value) => {
|
|
11872
|
-
const available = showAvailability
|
|
11873
|
-
? getAvailability(option.name, value, options, variants, selections)
|
|
11874
|
-
: true;
|
|
11875
|
-
return (React.createElement("option", { key: value, value: value, disabled: !available },
|
|
11876
|
-
value,
|
|
11877
|
-
!available ? ' (Unavailable)' : ''));
|
|
11878
|
-
}))) : (React.createElement("div", { className: "seekora-variant-buttons", style: { display: 'flex', flexWrap: 'wrap', gap: 8 } }, option.values.map((value) => {
|
|
12094
|
+
mode === 'dropdown' ? (React.createElement("div", { onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation() },
|
|
12095
|
+
React.createElement(CustomSelect, { className: "seekora-variant-dropdown", value: selected ?? '', onChange: (val) => onSelectionChange(option.name, val), placeholder: `Select ${option.name}`, "aria-label": `Select ${option.name}`, options: option.values.map((val) => {
|
|
12096
|
+
const available = showAvailability
|
|
12097
|
+
? getAvailability(option.name, val, options, variants, selections)
|
|
12098
|
+
: true;
|
|
12099
|
+
return {
|
|
12100
|
+
value: val,
|
|
12101
|
+
label: available ? val : `${val} (Unavailable)`,
|
|
12102
|
+
disabled: !available,
|
|
12103
|
+
};
|
|
12104
|
+
}), style: {
|
|
12105
|
+
minWidth: 120,
|
|
12106
|
+
'--seekora-select-border': 'var(--seekora-border-color, rgba(128,128,128,0.2))',
|
|
12107
|
+
'--seekora-select-bg': 'var(--seekora-bg-surface, transparent)',
|
|
12108
|
+
'--seekora-select-color': 'var(--seekora-text-primary, inherit)',
|
|
12109
|
+
} }))) : (React.createElement("div", { className: "seekora-variant-buttons", style: { display: 'flex', flexWrap: 'wrap', gap: 8 } }, option.values.map((value) => {
|
|
11879
12110
|
const isActive = selected === value;
|
|
11880
12111
|
const available = showAvailability
|
|
11881
12112
|
? getAvailability(option.name, value, options, variants, selections)
|
|
@@ -18647,6 +18878,7 @@ exports.Breadcrumb = Breadcrumb;
|
|
|
18647
18878
|
exports.CategoriesTabs = CategoriesTabs;
|
|
18648
18879
|
exports.ClearRefinements = ClearRefinements;
|
|
18649
18880
|
exports.CurrentRefinements = CurrentRefinements;
|
|
18881
|
+
exports.CustomSelect = CustomSelect;
|
|
18650
18882
|
exports.DocSearch = DocSearch;
|
|
18651
18883
|
exports.DocSearchButton = DocSearchButton;
|
|
18652
18884
|
exports.DropdownPanel = DropdownPanel;
|