@athoscommerce/snap-preact 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/cjs/components/src/components/Atoms/Price/Price.js +2 -2
  2. package/dist/cjs/components/src/components/Molecules/CustomComponentWrapper/CustomComponentWrapper.js +1 -1
  3. package/dist/cjs/components/src/components/Molecules/Slideshow/Slideshow.js +59 -30
  4. package/dist/cjs/components/src/components/Organisms/AutocompleteLayout/AutocompleteLayout.js +13 -8
  5. package/dist/cjs/components/src/components/Organisms/QuickviewLayout/QuickviewLayout.js +13 -8
  6. package/dist/cjs/components/src/hooks/useLayoutOptions.js +19 -14
  7. package/dist/cjs/components/src/utilities/mergeProps.js +647 -184
  8. package/dist/cjs/src/Snap.js +1 -1
  9. package/dist/cjs/src/Templates/SnapTemplates.js +17 -53
  10. package/dist/cjs/src/Templates/Stores/LibraryStore.js +2 -0
  11. package/dist/cjs/src/Templates/Stores/TemplateStore.js +37 -8
  12. package/dist/cjs/src/Templates/Stores/ThemeStore.js +125 -32
  13. package/dist/cjs/src/Templates/Stores/library/currencies/currencies.js +52 -29
  14. package/dist/cjs/src/Templates/Stores/library/plugins/shopify/pluginCurrency.js +5 -0
  15. package/dist/esm/components/src/components/Atoms/Price/Price.d.ts +2 -0
  16. package/dist/esm/components/src/components/Atoms/Price/Price.d.ts.map +1 -1
  17. package/dist/esm/components/src/components/Atoms/Price/Price.js +2 -2
  18. package/dist/esm/components/src/components/Molecules/CustomComponentWrapper/CustomComponentWrapper.d.ts.map +1 -1
  19. package/dist/esm/components/src/components/Molecules/CustomComponentWrapper/CustomComponentWrapper.js +1 -1
  20. package/dist/esm/components/src/components/Molecules/Select/Select.d.ts +2 -2
  21. package/dist/esm/components/src/components/Molecules/Select/Select.d.ts.map +1 -1
  22. package/dist/esm/components/src/components/Molecules/Slideshow/Slideshow.d.ts +1 -0
  23. package/dist/esm/components/src/components/Molecules/Slideshow/Slideshow.d.ts.map +1 -1
  24. package/dist/esm/components/src/components/Molecules/Slideshow/Slideshow.js +59 -30
  25. package/dist/esm/components/src/components/Organisms/AutocompleteLayout/AutocompleteLayout.d.ts.map +1 -1
  26. package/dist/esm/components/src/components/Organisms/AutocompleteLayout/AutocompleteLayout.js +13 -8
  27. package/dist/esm/components/src/components/Organisms/QuickviewLayout/QuickviewLayout.d.ts.map +1 -1
  28. package/dist/esm/components/src/components/Organisms/QuickviewLayout/QuickviewLayout.js +13 -8
  29. package/dist/esm/components/src/hooks/useLayoutOptions.d.ts.map +1 -1
  30. package/dist/esm/components/src/hooks/useLayoutOptions.js +19 -14
  31. package/dist/esm/components/src/providers/langComponents.d.ts +5 -0
  32. package/dist/esm/components/src/providers/langComponents.d.ts.map +1 -1
  33. package/dist/esm/components/src/providers/themeComponents.d.ts +214 -193
  34. package/dist/esm/components/src/providers/themeComponents.d.ts.map +1 -1
  35. package/dist/esm/components/src/utilities/mergeProps.d.ts +11 -0
  36. package/dist/esm/components/src/utilities/mergeProps.d.ts.map +1 -1
  37. package/dist/esm/components/src/utilities/mergeProps.js +647 -184
  38. package/dist/esm/src/Snap.js +1 -1
  39. package/dist/esm/src/Templates/SnapTemplates.d.ts +32 -19
  40. package/dist/esm/src/Templates/SnapTemplates.d.ts.map +1 -1
  41. package/dist/esm/src/Templates/SnapTemplates.js +16 -51
  42. package/dist/esm/src/Templates/Stores/LibraryStore.d.ts +7 -0
  43. package/dist/esm/src/Templates/Stores/LibraryStore.d.ts.map +1 -1
  44. package/dist/esm/src/Templates/Stores/LibraryStore.js +2 -0
  45. package/dist/esm/src/Templates/Stores/TemplateStore.d.ts +11 -7
  46. package/dist/esm/src/Templates/Stores/TemplateStore.d.ts.map +1 -1
  47. package/dist/esm/src/Templates/Stores/TemplateStore.js +31 -8
  48. package/dist/esm/src/Templates/Stores/ThemeStore.d.ts +5 -2
  49. package/dist/esm/src/Templates/Stores/ThemeStore.d.ts.map +1 -1
  50. package/dist/esm/src/Templates/Stores/ThemeStore.js +125 -32
  51. package/dist/esm/src/Templates/Stores/library/currencies/currencies.d.ts +27 -0
  52. package/dist/esm/src/Templates/Stores/library/currencies/currencies.d.ts.map +1 -1
  53. package/dist/esm/src/Templates/Stores/library/currencies/currencies.js +52 -29
  54. package/dist/esm/src/Templates/Stores/library/plugins/shopify/pluginCurrency.d.ts +2 -0
  55. package/dist/esm/src/Templates/Stores/library/plugins/shopify/pluginCurrency.d.ts.map +1 -0
  56. package/dist/esm/src/Templates/Stores/library/plugins/shopify/pluginCurrency.js +1 -0
  57. package/eslint/src/validate-config.cjs +520 -0
  58. package/package.json +13 -13
@@ -35,7 +35,7 @@ exports.Price = (0, mobx_react_lite_1.observer)((properties) => {
35
35
  treePath: globalTreePath,
36
36
  };
37
37
  const props = (0, utilities_1.mergeProps)('price', globalTheme, defaultProps, properties);
38
- const { lineThrough, value, format, symbol, decimalPlaces, padDecimalPlaces, thousandsSeparator, decimalSeparator, symbolAfter, raw, className, internalClassName, } = props;
38
+ const { lineThrough, value, format, symbol, decimalPlaces, padDecimalPlaces, thousandsSeparator, decimalSeparator, symbolAfter, code, showCode, raw, className, internalClassName, } = props;
39
39
  const { overrideElement, shouldRenderDefault } = (0, hooks_1.useCustomComponentOverride)('price', props);
40
40
  if (!shouldRenderDefault) {
41
41
  return overrideElement;
@@ -54,7 +54,7 @@ exports.Price = (0, mobx_react_lite_1.observer)((properties) => {
54
54
  }
55
55
  const styling = (0, utilities_1.mergeStyles)(props, defaultStyles);
56
56
  if (formattedPrice) {
57
- return raw ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: formattedPrice })) : ((0, jsx_runtime_1.jsx)(providers_1.CacheProvider, { children: (0, jsx_runtime_1.jsxs)("span", { ...styling, className: (0, classnames_1.default)('ss__price', { 'ss__price--strike': lineThrough }, className, internalClassName), children: [!format && symbol && !symbolAfter ? (0, jsx_runtime_1.jsx)("span", { className: 'ss__price__symbol', children: symbol }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}), (0, jsx_runtime_1.jsx)("span", { className: 'ss__price__value', children: formattedPrice }), !format && symbol && symbolAfter ? (0, jsx_runtime_1.jsx)("span", { className: 'ss__price__symbol', children: symbol }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {})] }) }));
57
+ return raw ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: formattedPrice })) : ((0, jsx_runtime_1.jsx)(providers_1.CacheProvider, { children: (0, jsx_runtime_1.jsxs)("span", { ...styling, className: (0, classnames_1.default)('ss__price', { 'ss__price--strike': lineThrough }, className, internalClassName), children: [!format && symbol && !symbolAfter ? (0, jsx_runtime_1.jsx)("span", { className: 'ss__price__symbol', children: symbol }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}), (0, jsx_runtime_1.jsx)("span", { className: 'ss__price__value', children: formattedPrice }), !format && symbol && symbolAfter ? (0, jsx_runtime_1.jsx)("span", { className: 'ss__price__symbol', children: symbol }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}), !format && showCode && code ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ['\u00A0', (0, jsx_runtime_1.jsx)("span", { className: 'ss__price__code', children: code })] })) : ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}))] }) }));
58
58
  }
59
59
  else {
60
60
  return null;
@@ -21,5 +21,5 @@ exports.CustomComponentWrapper = (0, mobx_react_lite_1.observer)((properties) =>
21
21
  const { children } = props;
22
22
  const styling = (0, utilities_1.mergeStyles)(props, defaultStyles);
23
23
  const path = styling['ss-path'];
24
- return ((0, jsx_runtime_1.jsx)(providers_1.CacheProvider, { children: (0, jsx_runtime_1.jsx)("div", { ...styling, children: (0, utilities_1.cloneWithProps)(children, { treePath: path, theme: globalTheme }) }) }));
24
+ return ((0, jsx_runtime_1.jsx)(providers_1.CacheProvider, { children: (0, jsx_runtime_1.jsx)("div", { ...styling, children: (0, utilities_1.cloneWithProps)(children, { treePath: path, theme: props.theme }) }) }));
25
25
  });
@@ -15,8 +15,6 @@ const Image_1 = require("../../Atoms/Image");
15
15
  const Button_1 = require("../../Atoms/Button");
16
16
  const hooks_2 = require("../../../hooks");
17
17
  const deepmerge_1 = __importDefault(require("deepmerge"));
18
- // Pointer travel (px) past which a press is treated as a drag rather than a click.
19
- const DRAG_CLICK_THRESHOLD = 5;
20
18
  const defaultStyles = ({ theme, slidesToShow = 1, slideWidth, gap = 16, overlayNavigation = false, showNavigation }) => {
21
19
  return (0, react_1.css)({
22
20
  position: 'relative',
@@ -61,8 +59,10 @@ const defaultStyles = ({ theme, slidesToShow = 1, slideWidth, gap = 16, overlayN
61
59
  height: '100%',
62
60
  objectFit: 'cover',
63
61
  display: 'block',
64
- // Prevent image dragging
62
+ // pointer-events:none isn't fully reliable for blocking native image drag
63
+ // this plus draggable={false} below covers all browsers
65
64
  pointerEvents: 'none',
65
+ WebkitUserDrag: 'none',
66
66
  },
67
67
  },
68
68
  '.ss__slideshow__slide--content': {
@@ -75,10 +75,6 @@ const defaultStyles = ({ theme, slidesToShow = 1, slideWidth, gap = 16, overlayN
75
75
  },
76
76
  '.ss__slideshow__slide--clickable': {
77
77
  cursor: 'pointer',
78
- '&:hover img': {
79
- opacity: 0.9,
80
- transition: 'opacity 0.2s ease',
81
- },
82
78
  '&:focus-visible': {
83
79
  outline: '2px solid #005fcc',
84
80
  outlineOffset: '2px',
@@ -168,9 +164,10 @@ exports.Slideshow = (0, mobx_react_lite_1.observer)((properties) => {
168
164
  ariaLabel: 'slideshow',
169
165
  touchDragging: true,
170
166
  dragThreshold: 50,
167
+ dragClickThreshold: 10,
171
168
  };
172
169
  const props = (0, utilities_1.mergeProps)('slideshow', globalTheme, defaultProps, properties);
173
- const { className, slides, fallbackImage, autoPlay, autoPlayInterval, showNavigation, showPagination, loop, alwaysShowNavigation, slidesToShow, slideImageAlt, centerInsufficientSlides, ariaLabel, ariaLabelledBy, disableStyles, slideWidth, startIndex, gap, treePath, overlayNavigation, dragThreshold, } = props;
170
+ const { className, slides, fallbackImage, autoPlay, autoPlayInterval, showNavigation, showPagination, loop, alwaysShowNavigation, slidesToShow, slideImageAlt, centerInsufficientSlides, ariaLabel, ariaLabelledBy, disableStyles, slideWidth, startIndex, gap, treePath, overlayNavigation, dragThreshold, dragClickThreshold, } = props;
174
171
  const { overrideElement, shouldRenderDefault } = (0, hooks_2.useCustomComponentOverride)('slideshow', props);
175
172
  if (!shouldRenderDefault) {
176
173
  return overrideElement;
@@ -180,6 +177,9 @@ exports.Slideshow = (0, mobx_react_lite_1.observer)((properties) => {
180
177
  Image: {
181
178
  // default props
182
179
  fallback: fallbackImage,
180
+ // images are draggable by default in every browser
181
+ // a native image drag can hijack our custom drag and eat the mouseup
182
+ draggable: false,
183
183
  // inherited props
184
184
  ...(0, utilities_1.defined)({
185
185
  disableStyles,
@@ -272,9 +272,17 @@ exports.Slideshow = (0, mobx_react_lite_1.observer)((properties) => {
272
272
  }, []);
273
273
  // Touch/Drag state
274
274
  const [isDragging, setIsDragging] = (0, hooks_1.useState)(false);
275
- const [startX, setStartX] = (0, hooks_1.useState)(0);
276
- const [currentX, setCurrentX] = (0, hooks_1.useState)(0);
275
+ // refs, not state - only read inside the drag handlers, never in render
276
+ // the document mouseup listener below is bound once per drag, so its closure goes stale
277
+ // refs stay current even from that stale closure, state would not
278
+ const startXRef = (0, hooks_1.useRef)(0);
279
+ const currentXRef = (0, hooks_1.useRef)(0);
277
280
  const [dragOffset, setDragOffset] = (0, hooks_1.useState)(0);
281
+ // the track's on-screen translateX (px) captured when the current drag began
282
+ // starting a drag turns off the CSS transition immediately
283
+ // without this, interrupting an in-flight transition would snap the track to its target
284
+ // null means there's no drag in progress
285
+ const dragBaseTranslatePxRef = (0, hooks_1.useRef)(null);
278
286
  // Normalize slides to SlideshowSlide format
279
287
  const normalizedSlides = slides.map((slide, index) => {
280
288
  if (typeof slide === 'string') {
@@ -366,15 +374,33 @@ exports.Slideshow = (0, mobx_react_lite_1.observer)((properties) => {
366
374
  if (totalSlides <= visibleSlides) {
367
375
  touchDragging = false;
368
376
  }
377
+ // how far you need to drag before it counts as a navigation
378
+ // smaller of dragThreshold and 30% of a slide's width
379
+ const getNavigationThreshold = () => {
380
+ const trackContainerWidth = trackRef.current?.parentElement?.offsetWidth || 0;
381
+ const slideWidthPx = trackContainerWidth / visibleSlides;
382
+ return Math.min(dragThreshold, slideWidthPx * 0.3);
383
+ };
369
384
  // Touch/Mouse event handlers for dragging
370
385
  const handleDragStart = (clientX) => {
371
386
  if (!touchDragging)
372
387
  return;
388
+ // capture the track's actual current position, mid-transition or not
389
+ // the track has no margin and fills its parent's width
390
+ // so the gap between their left edges is exactly the current translateX in px
391
+ dragBaseTranslatePxRef.current = null;
392
+ if (trackRef.current?.parentElement) {
393
+ dragBaseTranslatePxRef.current = trackRef.current.getBoundingClientRect().left - trackRef.current.parentElement.getBoundingClientRect().left;
394
+ }
373
395
  hasDraggedRef.current = false;
374
396
  setIsPlaying(false);
375
397
  setIsDragging(true);
376
- setStartX(clientX);
377
- setCurrentX(clientX);
398
+ // a previous drag's mouseup may never have arrived (e.g. a native image drag hijacked it)
399
+ // that would leave dragOffset stuck from that aborted drag
400
+ // reset it so this drag always starts clean, not stacked on stale movement
401
+ setDragOffset(0);
402
+ startXRef.current = clientX;
403
+ currentXRef.current = clientX;
378
404
  // Pause autoplay during drag
379
405
  if (intervalRef.current) {
380
406
  clearInterval(intervalRef.current);
@@ -383,11 +409,12 @@ exports.Slideshow = (0, mobx_react_lite_1.observer)((properties) => {
383
409
  const handleDragMove = (clientX) => {
384
410
  if (!isDragging || !touchDragging)
385
411
  return;
386
- setCurrentX(clientX);
387
- const diff = clientX - startX;
388
- // Past a few pixels of travel this is a drag, not a click — flag it so the trailing
389
- // click (fired after mouseup/touchend) doesn't trigger slide onClick handlers.
390
- if (Math.abs(diff) > DRAG_CLICK_THRESHOLD) {
412
+ currentXRef.current = clientX;
413
+ const diff = clientX - startXRef.current;
414
+ // past a few pixels this counts as a drag, not a click
415
+ // flags it so the trailing click after mouseup doesn't fire onClick
416
+ // clamped to the nav threshold so a drag big enough to navigate is always flagged
417
+ if (Math.abs(diff) > Math.min(dragClickThreshold, getNavigationThreshold())) {
391
418
  hasDraggedRef.current = true;
392
419
  }
393
420
  setDragOffset(diff);
@@ -395,10 +422,8 @@ exports.Slideshow = (0, mobx_react_lite_1.observer)((properties) => {
395
422
  const handleDragEnd = () => {
396
423
  if (!isDragging || !touchDragging)
397
424
  return;
398
- const diff = currentX - startX;
399
- const trackContainerWidth = trackRef.current?.parentElement?.offsetWidth || 0;
400
- const slideWidthPx = trackContainerWidth / visibleSlides;
401
- const threshold = Math.min(dragThreshold, slideWidthPx * 0.3); // 30% of slide width or dragThreshold, whichever is smaller
425
+ const diff = currentXRef.current - startXRef.current;
426
+ const threshold = getNavigationThreshold();
402
427
  if (Math.abs(diff) > threshold) {
403
428
  if (diff > 0 && (loop || currentIndex > 0)) {
404
429
  // Dragged right - go to previous
@@ -411,6 +436,7 @@ exports.Slideshow = (0, mobx_react_lite_1.observer)((properties) => {
411
436
  }
412
437
  setIsDragging(false);
413
438
  setDragOffset(0);
439
+ dragBaseTranslatePxRef.current = null;
414
440
  // Resume autoplay if it was playing
415
441
  if (isPlaying && normalizedSlides.length > computedSlidesToShow) {
416
442
  intervalRef.current = setInterval(() => {
@@ -553,7 +579,13 @@ exports.Slideshow = (0, mobx_react_lite_1.observer)((properties) => {
553
579
  // Simplified transform calculation
554
580
  let translateX;
555
581
  let translateUnit;
556
- if (slideWidth) {
582
+ if (isDragging && dragBaseTranslatePxRef.current !== null) {
583
+ // while dragging, move from the position captured in handleDragStart
584
+ // not from currentIndex, so an interrupted transition continues smoothly
585
+ translateX = dragBaseTranslatePxRef.current + dragOffset;
586
+ translateUnit = 'px';
587
+ }
588
+ else if (slideWidth) {
557
589
  // Fixed-width mode: translate by pixel amounts (slideWidth + gap per slide)
558
590
  const slideStepPx = slideWidth + (gap ?? 0);
559
591
  translateX = -(currentIndex * slideStepPx);
@@ -626,20 +658,17 @@ exports.Slideshow = (0, mobx_react_lite_1.observer)((properties) => {
626
658
  handleDragMove(touch.clientX);
627
659
  }
628
660
  : undefined, onTouchEnd: touchDragging ? handleDragEnd : undefined,
629
- // Mouse events (for desktop dragging)
661
+ // mouse events for desktop dragging
662
+ // move/up are handled by the document listeners below, not here
663
+ // that keeps the drag tracking even if the cursor leaves the track
664
+ // and avoids firing handleDragEnd twice on a release over the track
630
665
  // @ts-ignore - mouse events
631
666
  onMouseDown: touchDragging
632
667
  ? (event) => {
633
668
  event.preventDefault();
634
669
  handleDragStart(event.clientX);
635
670
  }
636
- : undefined,
637
- // Mouse events (for desktop dragging)
638
- // @ts-ignore - mouse events
639
- onMouseUp: touchDragging ? handleDragEnd : undefined,
640
- // Mouse events (for desktop dragging)
641
- // @ts-ignore - mouse events
642
- onMouseMove: touchDragging ? handleMouseMove : undefined, children: normalizedSlides.map((slide, index) => {
671
+ : undefined, children: normalizedSlides.map((slide, index) => {
643
672
  const isVisible = index >= currentIndex && index < currentIndex + computedSlidesToShow;
644
673
  const hasClickHandler = !!slide.onClick;
645
674
  const hasContent = !!slide.content;
@@ -463,38 +463,42 @@ exports.AutocompleteLayout = (0, mobx_react_lite_1.observer)((properties) => {
463
463
  RecommendationTemplateResultComponent = recs.RecommendationTemplateResultComponent;
464
464
  recsController = recs.recsController;
465
465
  }
466
- const findModule = (module) => {
466
+ const findModule = (module, rowCounter) => {
467
467
  //new row
468
468
  if (typeof module !== 'string') {
469
- const children = module?.map((subModule) => findModule(subModule));
469
+ const children = module?.map((subModule) => findModule(subModule, rowCounter));
470
470
  const hasContent = module.some((subModule, i) => subModule !== '_' && children[i]);
471
471
  if (!hasContent)
472
472
  return null;
473
- return (0, jsx_runtime_1.jsx)("div", { className: "ss__autocomplete__row", children: children });
473
+ return (0, jsx_runtime_1.jsx)("div", { className: `ss__autocomplete__row ss__autocomplete__row--${rowCounter.value++}`, children: children });
474
474
  }
475
475
  if (module == 'c1' && column1?.layout?.length) {
476
- const children = column1.layout.map((m) => findModule(m));
476
+ const c1RowCounter = { value: 0 };
477
+ const children = column1.layout.map((m) => findModule(m, c1RowCounter));
477
478
  const hasContent = column1.layout.some((m, i) => (Array.isArray(m) ? Boolean(children[i]) : m !== '_' && Boolean(children[i])));
478
479
  if (!hasContent)
479
480
  return null;
480
481
  return (0, jsx_runtime_1.jsx)("div", { className: "ss__autocomplete__column ss__autocomplete__column--c1", children: children });
481
482
  }
482
483
  if (module == 'c2' && column2?.layout?.length) {
483
- const children = column2.layout.map((m) => findModule(m));
484
+ const c2RowCounter = { value: 0 };
485
+ const children = column2.layout.map((m) => findModule(m, c2RowCounter));
484
486
  const hasContent = column2.layout.some((m, i) => (Array.isArray(m) ? Boolean(children[i]) : m !== '_' && Boolean(children[i])));
485
487
  if (!hasContent)
486
488
  return null;
487
489
  return (0, jsx_runtime_1.jsx)("div", { className: "ss__autocomplete__column ss__autocomplete__column--c2", children: children });
488
490
  }
489
491
  if (module == 'c3' && column3?.layout?.length) {
490
- const children = column3.layout.map((m) => findModule(m));
492
+ const c3RowCounter = { value: 0 };
493
+ const children = column3.layout.map((m) => findModule(m, c3RowCounter));
491
494
  const hasContent = column3.layout.some((m, i) => (Array.isArray(m) ? Boolean(children[i]) : m !== '_' && Boolean(children[i])));
492
495
  if (!hasContent)
493
496
  return null;
494
497
  return (0, jsx_runtime_1.jsx)("div", { className: "ss__autocomplete__column ss__autocomplete__column--c3", children: children });
495
498
  }
496
499
  if (module == 'c4' && column4?.layout?.length) {
497
- const children = column4.layout.map((m) => findModule(m));
500
+ const c4RowCounter = { value: 0 };
501
+ const children = column4.layout.map((m) => findModule(m, c4RowCounter));
498
502
  const hasContent = column4.layout.some((m, i) => (Array.isArray(m) ? Boolean(children[i]) : m !== '_' && Boolean(children[i])));
499
503
  if (!hasContent)
500
504
  return null;
@@ -573,8 +577,9 @@ exports.AutocompleteLayout = (0, mobx_react_lite_1.observer)((properties) => {
573
577
  controller.log.warn(`unsupported layout found. ${props.layout}`);
574
578
  layout = [];
575
579
  }
580
+ const topRowCounter = { value: 0 };
576
581
  /***************************************/
577
582
  return visible && layout?.length ? ((0, jsx_runtime_1.jsx)(providers_1.CacheProvider, { children: (0, jsx_runtime_1.jsxs)("div", { ...styling, className: (0, classnames_1.default)('ss__autocomplete', { 'ss__autocomplete--terms': props.layout === 'terms' }, { 'ss__autocomplete--mobile': props.layout === 'mobile' }, { 'ss__autocomplete--desktop': props.layout === 'desktop' }, { 'ss__autocomplete--tablet': props.layout === 'tablet' }, className, internalClassName), onClick: (e) => e.stopPropagation(), ref: (e) => (0, hooks_2.useA11y)(e, 0, false, onReset), children: [(0, jsx_runtime_1.jsx)("span", { role: 'link', ref: (e) => (0, hooks_2.useA11y)(e), onClick: () => props.onReset && props.onReset(), className: "ss__autocomplete__close-button", style: { position: 'absolute', top: '-10000000px', left: '-1000000px' }, ...mergedLang.closeButton?.all }), layout?.map((module) => {
578
- return findModule(module);
583
+ return findModule(module, topRowCounter);
579
584
  })] }) })) : null;
580
585
  });
@@ -478,38 +478,42 @@ exports.QuickviewLayout = (0, mobx_react_lite_1.observer)((properties) => {
478
478
  })), theme: props.theme, treePath: treePath, ...(0, utilities_1.defined)({ disableStyles }) })) : null;
479
479
  // Module renderers. Each returns the corresponding region, or null when it has nothing to
480
480
  // show (so empty columns/rows collapse). Columns (c1/c2) recurse into their own layouts.
481
- const findModule = (module) => {
481
+ const findModule = (module, rowCounter) => {
482
482
  // new row
483
483
  if (typeof module !== 'string') {
484
- const children = module.map((subModule) => findModule(subModule));
484
+ const children = module.map((subModule) => findModule(subModule, rowCounter));
485
485
  const hasContent = module.some((subModule, i) => subModule !== '_' && children[i]);
486
486
  if (!hasContent)
487
487
  return null;
488
- return (0, jsx_runtime_1.jsx)("div", { className: "ss__quickview__row", children: children });
488
+ return (0, jsx_runtime_1.jsx)("div", { className: `ss__quickview__row ss__quickview__row--${rowCounter.value++}`, children: children });
489
489
  }
490
490
  if (module == 'c1' && column1?.layout?.length) {
491
- const children = column1.layout.map((m) => findModule(m));
491
+ const c1RowCounter = { value: 0 };
492
+ const children = column1.layout.map((m) => findModule(m, c1RowCounter));
492
493
  const hasContent = column1.layout.some((m, i) => (Array.isArray(m) ? Boolean(children[i]) : m !== '_' && Boolean(children[i])));
493
494
  if (!hasContent)
494
495
  return null;
495
496
  return (0, jsx_runtime_1.jsx)("div", { className: "ss__quickview__column ss__quickview__column--c1", children: children });
496
497
  }
497
498
  if (module == 'c2' && column2?.layout?.length) {
498
- const children = column2.layout.map((m) => findModule(m));
499
+ const c2RowCounter = { value: 0 };
500
+ const children = column2.layout.map((m) => findModule(m, c2RowCounter));
499
501
  const hasContent = column2.layout.some((m, i) => (Array.isArray(m) ? Boolean(children[i]) : m !== '_' && Boolean(children[i])));
500
502
  if (!hasContent)
501
503
  return null;
502
504
  return (0, jsx_runtime_1.jsx)("div", { className: "ss__quickview__column ss__quickview__column--c2", children: children });
503
505
  }
504
506
  if (module == 'c3' && column3?.layout?.length) {
505
- const children = column3.layout.map((m) => findModule(m));
507
+ const c3RowCounter = { value: 0 };
508
+ const children = column3.layout.map((m) => findModule(m, c3RowCounter));
506
509
  const hasContent = column3.layout.some((m, i) => (Array.isArray(m) ? Boolean(children[i]) : m !== '_' && Boolean(children[i])));
507
510
  if (!hasContent)
508
511
  return null;
509
512
  return (0, jsx_runtime_1.jsx)("div", { className: "ss__quickview__column ss__quickview__column--c3", children: children });
510
513
  }
511
514
  if (module == 'c4' && column4?.layout?.length) {
512
- const children = column4.layout.map((m) => findModule(m));
515
+ const c4RowCounter = { value: 0 };
516
+ const children = column4.layout.map((m) => findModule(m, c4RowCounter));
513
517
  const hasContent = column4.layout.some((m, i) => (Array.isArray(m) ? Boolean(children[i]) : m !== '_' && Boolean(children[i])));
514
518
  if (!hasContent)
515
519
  return null;
@@ -607,6 +611,7 @@ exports.QuickviewLayout = (0, mobx_react_lite_1.observer)((properties) => {
607
611
  quickviewManager.sourceController?.log.warn(`unsupported layout found. ${props.layout}`);
608
612
  layout = [];
609
613
  }
614
+ const topRowCounter = { value: 0 };
610
615
  const closeButton = ((0, jsx_runtime_1.jsx)(Button_1.Button, { name: "close", internalClassName: "ss__quickview__close", icon: "close-thin", lang: { button: lang.closeButton }, onClick: () => onClose(), theme: props.theme, treePath: treePath, ...(0, utilities_1.defined)({ disableStyles }) }));
611
- return ((0, jsx_runtime_1.jsx)(providers_1.CacheProvider, { children: (0, jsx_runtime_1.jsx)("div", { ...styling, className: (0, classnames_1.default)('ss__quickview', className, internalClassName), children: error || loading || product ? ((0, jsx_runtime_1.jsxs)("div", { ...contentProps, children: [closeButton, error ? ((0, jsx_runtime_1.jsx)("div", { className: "ss__quickview__error", role: "alert", children: error.message })) : loading ? ((0, jsx_runtime_1.jsx)("div", { className: "ss__quickview__loading", ...mergedLang.loadingText?.all })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [layout?.map((module) => findModule(module)), (0, jsx_runtime_1.jsx)(Gallery_1.Gallery, { images: galleryImages, open: galleryOpen, startIndex: galleryIndex, onClose: () => setGalleryOpen(false), alt: name, theme: props.theme, treePath: treePath, ...(0, utilities_1.defined)({ disableStyles }) })] }))] })) : null }) }));
616
+ return ((0, jsx_runtime_1.jsx)(providers_1.CacheProvider, { children: (0, jsx_runtime_1.jsx)("div", { ...styling, className: (0, classnames_1.default)('ss__quickview', className, internalClassName), children: error || loading || product ? ((0, jsx_runtime_1.jsxs)("div", { ...contentProps, children: [closeButton, error ? ((0, jsx_runtime_1.jsx)("div", { className: "ss__quickview__error", role: "alert", children: error.message })) : loading ? ((0, jsx_runtime_1.jsx)("div", { className: "ss__quickview__loading", ...mergedLang.loadingText?.all })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [layout?.map((module) => findModule(module, topRowCounter)), (0, jsx_runtime_1.jsx)(Gallery_1.Gallery, { images: galleryImages, open: galleryOpen, startIndex: galleryIndex, onClose: () => setGalleryOpen(false), alt: name, theme: props.theme, treePath: treePath, ...(0, utilities_1.defined)({ disableStyles }) })] }))] })) : null }) }));
612
617
  });
@@ -54,20 +54,25 @@ const useLayoutOptions = (props, globalTheme) => {
54
54
  const templateComponent = props.treePath.split(' ')[0];
55
55
  // we only want to use overrides if one of the toolbars is actually rendering the layoutSelector
56
56
  Object.keys(globalTheme.components).forEach((key) => {
57
- const paths = key.split(' ');
58
- const componentTypeAndName = paths.splice(-1).pop() ?? '';
59
- const [componentType] = componentTypeAndName.split('.');
60
- if (globalTheme.components &&
61
- componentType == 'toolbar' &&
62
- (paths[0] == templateComponent ||
63
- paths[0] == `*${templateComponent}` ||
64
- paths[0] == `*(M)${templateComponent}` ||
65
- paths[0] == `*(T)${templateComponent}` ||
66
- paths[0] == `*(D)${templateComponent}` ||
67
- paths[0] == `(M)${templateComponent}` ||
68
- paths[0] == `(T)${templateComponent}` ||
69
- paths[0] == `(D)${templateComponent}` ||
70
- !paths.length)) {
57
+ // a key may target multiple treepaths as a comma-separated list (see ThemeStore's
58
+ // prefixComponentKeys/normalizeCommaSeparatedKeys) - it applies if ANY part matches
59
+ const parts = key.includes(',') ? key.split(/\s*,\s*/) : [key];
60
+ const matchesToolbar = parts.some((part) => {
61
+ const paths = part.split(' ');
62
+ const componentTypeAndName = paths.splice(-1).pop() ?? '';
63
+ const [componentType] = componentTypeAndName.split('.');
64
+ return (componentType == 'toolbar' &&
65
+ (paths[0] == templateComponent ||
66
+ paths[0] == `*${templateComponent}` ||
67
+ paths[0] == `*(M)${templateComponent}` ||
68
+ paths[0] == `*(T)${templateComponent}` ||
69
+ paths[0] == `*(D)${templateComponent}` ||
70
+ paths[0] == `(M)${templateComponent}` ||
71
+ paths[0] == `(T)${templateComponent}` ||
72
+ paths[0] == `(D)${templateComponent}` ||
73
+ !paths.length));
74
+ });
75
+ if (globalTheme.components && matchesToolbar) {
71
76
  const toolbarConfig = globalTheme.components[key];
72
77
  if (toolbarConfig?.layout && toolbarConfig.layout.toString().indexOf('layoutSelector') > -1) {
73
78
  shouldUseOverrides = true;