@thefittingroom/shop-ui 5.0.26 → 5.0.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.
Files changed (2) hide show
  1. package/dist/index.js +296 -105
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14100,6 +14100,40 @@ function _init$7() {
14100
14100
  useMainStore.setState({
14101
14101
  fittingRoom: items
14102
14102
  });
14103
+ if (typeof window !== "undefined") {
14104
+ window.addEventListener("storage", (e) => {
14105
+ if (e.key !== STORAGE_KEY) {
14106
+ return;
14107
+ }
14108
+ useMainStore.setState({
14109
+ fittingRoom: readFittingRoom(brandId)
14110
+ });
14111
+ });
14112
+ }
14113
+ }
14114
+ function resolveCurrentProductCsaId(productId, size, color) {
14115
+ if (size == null) {
14116
+ return null;
14117
+ }
14118
+ const {
14119
+ currentProduct
14120
+ } = getStaticData();
14121
+ if (!currentProduct) {
14122
+ return null;
14123
+ }
14124
+ const stored = useMainStore.getState().productData[productId];
14125
+ if (!stored || "error" in stored) {
14126
+ return null;
14127
+ }
14128
+ const sizeRec = stored.sizeFitRecommendation.available_sizes.find((s) => getSizeLabelFromSize(s) === size);
14129
+ if (!sizeRec) {
14130
+ return null;
14131
+ }
14132
+ const csa = sizeRec.colorway_size_assets.find((c) => {
14133
+ const variant = currentProduct.variants.find((v) => v.sku === c.sku);
14134
+ return variant?.color === color;
14135
+ });
14136
+ return csa?.id ?? null;
14103
14137
  }
14104
14138
  async function addFittingRoomItem(productId, handle, isPdp) {
14105
14139
  const state = useMainStore.getState();
@@ -14108,9 +14142,7 @@ async function addFittingRoomItem(productId, handle, isPdp) {
14108
14142
  handle,
14109
14143
  isPdp
14110
14144
  });
14111
- let size = null;
14112
14145
  let color = null;
14113
- let colorwaySizeAssetId = null;
14114
14146
  let resolvedHandle = handle;
14115
14147
  if (isPdp) {
14116
14148
  const {
@@ -14122,37 +14154,59 @@ async function addFittingRoomItem(productId, handle, isPdp) {
14122
14154
  }
14123
14155
  try {
14124
14156
  const selection = await currentProduct.getSelectedOptions();
14125
- size = selection.size || null;
14126
14157
  color = selection.color;
14127
14158
  } catch (error) {
14128
14159
  logger$g.logWarn("Failed to read selected options from currentProduct", {
14129
14160
  error
14130
14161
  });
14131
14162
  }
14132
- const stored = state.productData[productId];
14133
- if (stored && !("error" in stored) && size != null) {
14134
- const sizeRec = stored.sizeFitRecommendation.available_sizes.find((s) => getSizeLabelFromSize(s) === size);
14135
- if (sizeRec) {
14136
- const csa = sizeRec.colorway_size_assets.find((c) => {
14137
- const variant = currentProduct.variants.find((v) => v.sku === c.sku);
14138
- return variant?.color === color;
14139
- });
14140
- if (csa) {
14141
- colorwaySizeAssetId = csa.id;
14142
- }
14143
- }
14144
- }
14145
14163
  }
14146
14164
  }
14147
14165
  state.addToFittingRoom({
14148
14166
  externalId: productId,
14149
14167
  handle: resolvedHandle,
14150
- size,
14168
+ size: null,
14151
14169
  color,
14152
- colorwaySizeAssetId,
14170
+ colorwaySizeAssetId: null,
14153
14171
  addedAt: Date.now()
14154
14172
  });
14155
14173
  }
14174
+ async function syncCurrentProductSelection() {
14175
+ const {
14176
+ currentProduct
14177
+ } = getStaticData();
14178
+ if (!currentProduct) {
14179
+ return;
14180
+ }
14181
+ const state = useMainStore.getState();
14182
+ const existing = state.fittingRoom.find((item) => item.externalId === currentProduct.externalId);
14183
+ if (!existing) {
14184
+ return;
14185
+ }
14186
+ let color = null;
14187
+ try {
14188
+ const selection = await currentProduct.getSelectedOptions();
14189
+ color = selection.color;
14190
+ } catch (error) {
14191
+ logger$g.logWarn("syncCurrentProductSelection: failed to read selected options", {
14192
+ error
14193
+ });
14194
+ return;
14195
+ }
14196
+ if (color === existing.color) {
14197
+ return;
14198
+ }
14199
+ const colorwaySizeAssetId = resolveCurrentProductCsaId(currentProduct.externalId, existing.size, color);
14200
+ state.updateFittingRoomItem(currentProduct.externalId, {
14201
+ color,
14202
+ colorwaySizeAssetId
14203
+ });
14204
+ logger$g.logDebug("{{ts}} - Synced PDP color into fitting-room", {
14205
+ externalId: currentProduct.externalId,
14206
+ color,
14207
+ colorwaySizeAssetId
14208
+ });
14209
+ }
14156
14210
  async function toggleFittingRoomItem(productId, handle, isPdp) {
14157
14211
  const state = useMainStore.getState();
14158
14212
  const isInFittingRoom = state.fittingRoom.some((item) => item.externalId === productId);
@@ -42201,7 +42255,12 @@ function AvatarControls({
42201
42255
  // clip rendered the text at fractional opacity while it reflowed each
42202
42256
  // frame, which subpixel-shimmered. The max-width clip alone reveals
42203
42257
  // the label cleanly at full opacity.
42204
- transition: "max-width 500ms cubic-bezier(0.22, 1, 0.36, 1)"
42258
+ transition: "max-width 500ms cubic-bezier(0.22, 1, 0.36, 1)",
42259
+ // Belt-and-suspenders with pillBaseStyle on the parent — rapid clicks
42260
+ // on the avatar's rotation chevrons can otherwise extend a triple-
42261
+ // click selection into these labels.
42262
+ userSelect: "none",
42263
+ WebkitUserSelect: "none"
42205
42264
  },
42206
42265
  pillLabelCollapsed: {
42207
42266
  maxWidth: 0
@@ -42317,37 +42376,43 @@ function MobileTuckControl({
42317
42376
  ] }) });
42318
42377
  }
42319
42378
  const DRAG_STEP_PX = 50;
42320
- function useFrameRotation(frameUrls, setSelectedFrameIndex) {
42379
+ const AXIS_LOCK_PX$1 = 8;
42380
+ function applyDragSteps(deltaX, rotateLeft, rotateRight) {
42381
+ const steps = Math.floor(Math.abs(deltaX) / DRAG_STEP_PX);
42382
+ if (steps === 0) {
42383
+ return 0;
42384
+ }
42385
+ const fn = deltaX > 0 ? rotateRight : rotateLeft;
42386
+ for (let i = 0; i < steps; i++) {
42387
+ fn();
42388
+ }
42389
+ return (deltaX > 0 ? 1 : -1) * steps * DRAG_STEP_PX;
42390
+ }
42391
+ function useFrameRotation(frameUrls, setSelectedFrameIndex, onUserInteract) {
42321
42392
  const frameCount = frameUrls?.length ?? 0;
42322
42393
  const rotateLeft = reactExports.useCallback(() => {
42394
+ onUserInteract?.();
42323
42395
  setSelectedFrameIndex((prev2) => {
42324
42396
  if (prev2 == null || frameCount === 0) {
42325
42397
  return prev2;
42326
42398
  }
42327
42399
  return prev2 === 0 ? frameCount - 1 : prev2 - 1;
42328
42400
  });
42329
- }, [frameCount, setSelectedFrameIndex]);
42401
+ }, [frameCount, onUserInteract, setSelectedFrameIndex]);
42330
42402
  const rotateRight = reactExports.useCallback(() => {
42403
+ onUserInteract?.();
42331
42404
  setSelectedFrameIndex((prev2) => {
42332
42405
  if (prev2 == null || frameCount === 0) {
42333
42406
  return prev2;
42334
42407
  }
42335
42408
  return prev2 === frameCount - 1 ? 0 : prev2 + 1;
42336
42409
  });
42337
- }, [frameCount, setSelectedFrameIndex]);
42410
+ }, [frameCount, onUserInteract, setSelectedFrameIndex]);
42338
42411
  const handleMouseDragStart = reactExports.useCallback((e) => {
42339
42412
  e.preventDefault();
42340
42413
  let startX = e.clientX;
42341
42414
  const onMove = (move) => {
42342
- const deltaX = move.clientX - startX;
42343
- if (Math.abs(deltaX) >= DRAG_STEP_PX) {
42344
- if (deltaX > 0) {
42345
- rotateRight();
42346
- } else {
42347
- rotateLeft();
42348
- }
42349
- startX = move.clientX;
42350
- }
42415
+ startX += applyDragSteps(move.clientX - startX, rotateLeft, rotateRight);
42351
42416
  };
42352
42417
  const onUp = () => {
42353
42418
  window.removeEventListener("mousemove", onMove);
@@ -42357,25 +42422,37 @@ function useFrameRotation(frameUrls, setSelectedFrameIndex) {
42357
42422
  window.addEventListener("mouseup", onUp);
42358
42423
  }, [rotateLeft, rotateRight]);
42359
42424
  const handleTouchDragStart = reactExports.useCallback((e) => {
42360
- e.preventDefault();
42361
42425
  let startX = e.touches[0].clientX;
42426
+ const startY = e.touches[0].clientY;
42427
+ let mode = "unknown";
42362
42428
  const onMove = (move) => {
42363
- const deltaX = move.touches[0].clientX - startX;
42364
- if (Math.abs(deltaX) >= DRAG_STEP_PX) {
42365
- if (deltaX > 0) {
42366
- rotateRight();
42367
- } else {
42368
- rotateLeft();
42429
+ const currentX = move.touches[0].clientX;
42430
+ const currentY = move.touches[0].clientY;
42431
+ if (mode === "unknown") {
42432
+ const absDx = Math.abs(currentX - startX);
42433
+ const absDy = Math.abs(currentY - startY);
42434
+ if (absDx < AXIS_LOCK_PX$1 && absDy < AXIS_LOCK_PX$1) {
42435
+ return;
42369
42436
  }
42370
- startX = move.touches[0].clientX;
42437
+ mode = absDx >= absDy ? "horizontal" : "vertical";
42438
+ startX = currentX;
42371
42439
  }
42440
+ if (mode !== "horizontal") {
42441
+ return;
42442
+ }
42443
+ move.preventDefault();
42444
+ startX += applyDragSteps(currentX - startX, rotateLeft, rotateRight);
42372
42445
  };
42373
42446
  const onEnd = () => {
42374
42447
  window.removeEventListener("touchmove", onMove);
42375
42448
  window.removeEventListener("touchend", onEnd);
42449
+ window.removeEventListener("touchcancel", onEnd);
42376
42450
  };
42377
- window.addEventListener("touchmove", onMove);
42451
+ window.addEventListener("touchmove", onMove, {
42452
+ passive: false
42453
+ });
42378
42454
  window.addEventListener("touchend", onEnd);
42455
+ window.addEventListener("touchcancel", onEnd);
42379
42456
  }, [rotateLeft, rotateRight]);
42380
42457
  return {
42381
42458
  rotateLeft,
@@ -42390,7 +42467,8 @@ function AvatarFrameViewer({
42390
42467
  setSelectedFrameIndex,
42391
42468
  imageContainerStyle,
42392
42469
  imageStyle,
42393
- loadingT = "quick-view.avatar_loading"
42470
+ loadingT = "quick-view.avatar_loading",
42471
+ onUserInteract
42394
42472
  }) {
42395
42473
  const css2 = useCss((_theme) => ({
42396
42474
  imageContainer: {
@@ -42398,21 +42476,31 @@ function AvatarFrameViewer({
42398
42476
  },
42399
42477
  image: {
42400
42478
  objectFit: "contain",
42401
- cursor: "grab"
42479
+ cursor: "grab",
42480
+ // Reserve horizontal touch gestures for our rotation handler — the
42481
+ // browser can still pan vertically natively. Without this, the
42482
+ // browser may start consuming a horizontal swipe as a scroll/zoom
42483
+ // before our touchmove listener can preventDefault, which produced
42484
+ // the "janky first swipe" reports.
42485
+ touchAction: "pan-y"
42402
42486
  },
42403
42487
  chevronLeftContainer: {
42404
42488
  position: "absolute",
42405
42489
  top: "50%",
42406
42490
  left: "0",
42407
42491
  transform: "translateY(-50%)",
42408
- cursor: "pointer"
42492
+ cursor: "pointer",
42493
+ userSelect: "none",
42494
+ WebkitUserSelect: "none"
42409
42495
  },
42410
42496
  chevronRightContainer: {
42411
42497
  position: "absolute",
42412
42498
  top: "50%",
42413
42499
  right: "0",
42414
42500
  transform: "translateY(-50%)",
42415
- cursor: "pointer"
42501
+ cursor: "pointer",
42502
+ userSelect: "none",
42503
+ WebkitUserSelect: "none"
42416
42504
  },
42417
42505
  chevronIcon: {
42418
42506
  width: "48px",
@@ -42420,46 +42508,83 @@ function AvatarFrameViewer({
42420
42508
  }
42421
42509
  }));
42422
42510
  reactExports.useEffect(() => {
42423
- if (!frameUrls || frameUrls.length === 0 || selectedFrameIndex != null) {
42424
- return;
42511
+ if (frameUrls && frameUrls.length > 0 && selectedFrameIndex == null) {
42512
+ setSelectedFrameIndex(0);
42425
42513
  }
42426
- let currentFrameIndex = 0;
42427
- setSelectedFrameIndex(currentFrameIndex);
42428
- const intervalId = setInterval(() => {
42429
- currentFrameIndex = (currentFrameIndex + 1) % frameUrls.length;
42430
- setSelectedFrameIndex(currentFrameIndex);
42431
- if (currentFrameIndex === 0) {
42432
- clearInterval(intervalId);
42433
- }
42434
- }, 500);
42435
- return () => clearInterval(intervalId);
42436
42514
  }, [frameUrls, selectedFrameIndex, setSelectedFrameIndex]);
42437
42515
  const {
42438
42516
  rotateLeft,
42439
42517
  rotateRight,
42440
42518
  handleMouseDragStart,
42441
42519
  handleTouchDragStart
42442
- } = useFrameRotation(frameUrls, setSelectedFrameIndex);
42520
+ } = useFrameRotation(frameUrls, setSelectedFrameIndex, onUserInteract);
42443
42521
  if (!frameUrls || selectedFrameIndex == null) {
42444
42522
  return /* @__PURE__ */ jsx$1(Loading, { t: loadingT });
42445
42523
  }
42446
42524
  return /* @__PURE__ */ jsxs("div", { css: css2.imageContainer, style: imageContainerStyle, children: [
42447
42525
  /* @__PURE__ */ jsx$1("img", { src: frameUrls[selectedFrameIndex], css: css2.image, style: imageStyle, onMouseDown: handleMouseDragStart, onTouchStart: handleTouchDragStart }),
42448
- /* @__PURE__ */ jsx$1("div", { css: css2.chevronLeftContainer, onClick: rotateLeft, children: /* @__PURE__ */ jsx$1(SvgChevronLeft, { css: css2.chevronIcon }) }),
42449
- /* @__PURE__ */ jsx$1("div", { css: css2.chevronRightContainer, onClick: rotateRight, children: /* @__PURE__ */ jsx$1(SvgChevronRight, { css: css2.chevronIcon }) })
42526
+ /* @__PURE__ */ jsx$1("div", { role: "button", "aria-label": "Rotate left", css: css2.chevronLeftContainer, onMouseDown: (e) => e.preventDefault(), onClick: rotateLeft, children: /* @__PURE__ */ jsx$1(SvgChevronLeft, { css: css2.chevronIcon }) }),
42527
+ /* @__PURE__ */ jsx$1("div", { role: "button", "aria-label": "Rotate right", css: css2.chevronRightContainer, onMouseDown: (e) => e.preventDefault(), onClick: rotateRight, children: /* @__PURE__ */ jsx$1(SvgChevronRight, { css: css2.chevronIcon }) })
42450
42528
  ] });
42451
42529
  }
42530
+ const AUTO_ROTATE_DURATION_MS = 4e3;
42531
+ function useAutoRotate(trigger, frameUrls, selectedFrameIndex, setSelectedFrameIndex) {
42532
+ const lastFiredRef = reactExports.useRef(void 0);
42533
+ const intervalIdRef = reactExports.useRef(null);
42534
+ const indexRef = reactExports.useRef(selectedFrameIndex);
42535
+ reactExports.useEffect(() => {
42536
+ indexRef.current = selectedFrameIndex;
42537
+ }, [selectedFrameIndex]);
42538
+ const cancelAutoRotate = reactExports.useCallback(() => {
42539
+ if (intervalIdRef.current !== null) {
42540
+ clearInterval(intervalIdRef.current);
42541
+ intervalIdRef.current = null;
42542
+ }
42543
+ }, []);
42544
+ const frameCount = frameUrls?.length ?? 0;
42545
+ reactExports.useEffect(() => {
42546
+ if (trigger === void 0) {
42547
+ return;
42548
+ }
42549
+ if (frameCount === 0) {
42550
+ return;
42551
+ }
42552
+ if (trigger === lastFiredRef.current) {
42553
+ return;
42554
+ }
42555
+ const tickMs = Math.round(AUTO_ROTATE_DURATION_MS / frameCount);
42556
+ const startFrameIndex = (indexRef.current ?? 0) % frameCount;
42557
+ let currentFrameIndex = startFrameIndex;
42558
+ let didFirstTick = false;
42559
+ intervalIdRef.current = setInterval(() => {
42560
+ if (!didFirstTick) {
42561
+ didFirstTick = true;
42562
+ lastFiredRef.current = trigger;
42563
+ }
42564
+ const nextFrameIndex = (currentFrameIndex + 1) % frameCount;
42565
+ currentFrameIndex = nextFrameIndex;
42566
+ setSelectedFrameIndex(nextFrameIndex);
42567
+ if (nextFrameIndex === startFrameIndex) {
42568
+ cancelAutoRotate();
42569
+ }
42570
+ }, tickMs);
42571
+ return cancelAutoRotate;
42572
+ }, [trigger, frameCount, setSelectedFrameIndex, cancelAutoRotate]);
42573
+ return cancelAutoRotate;
42574
+ }
42452
42575
  function AvatarPane({
42453
42576
  frameUrls,
42454
42577
  hasSelection,
42455
42578
  controls,
42456
42579
  mobileFullscreen,
42457
42580
  selectedFrameIndex: indexProp,
42458
- setSelectedFrameIndex: setIndexProp
42581
+ setSelectedFrameIndex: setIndexProp,
42582
+ autoRotateTrigger
42459
42583
  }) {
42460
42584
  const [localFrameIndex, setLocalFrameIndex] = reactExports.useState(null);
42461
42585
  const selectedFrameIndex = indexProp !== void 0 ? indexProp : localFrameIndex;
42462
42586
  const setSelectedFrameIndex = setIndexProp ?? setLocalFrameIndex;
42587
+ const cancelAutoRotate = useAutoRotate(autoRotateTrigger, frameUrls, selectedFrameIndex, setSelectedFrameIndex);
42463
42588
  const css2 = useCss((theme) => ({
42464
42589
  container: {
42465
42590
  width: "100%",
@@ -42517,7 +42642,7 @@ function AvatarPane({
42517
42642
  }
42518
42643
  }));
42519
42644
  if (frameUrls && frameUrls.length > 0) {
42520
- const viewer = /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: css2.frameContainer, imageStyle: css2.frameImage, loadingT: "quick-view.avatar_loading" });
42645
+ const viewer = /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: css2.frameContainer, imageStyle: css2.frameImage, loadingT: "quick-view.avatar_loading", onUserInteract: cancelAutoRotate });
42521
42646
  if (mobileFullscreen) {
42522
42647
  return /* @__PURE__ */ jsxs("div", { css: css2.mobileContainer, children: [
42523
42648
  /* @__PURE__ */ jsxs("div", { css: css2.mobileFrameSlot, children: [
@@ -42644,8 +42769,9 @@ function ProductCard({
42644
42769
  onClick();
42645
42770
  };
42646
42771
  const name2 = item.merchantProduct?.productName ?? item.externalId;
42647
- const imageUrl = item.merchantProduct?.imageUrl ?? null;
42648
- const price = item.merchantProduct?.variants[0]?.priceFormatted ?? null;
42772
+ const selectedVariant = item.merchantProduct?.variants.find((v) => v.color === item.storage.color && (!item.storage.size || v.size === item.storage.size));
42773
+ const imageUrl = selectedVariant?.imageUrl ?? item.merchantProduct?.imageUrl ?? null;
42774
+ const price = selectedVariant?.priceFormatted ?? item.merchantProduct?.variants[0]?.priceFormatted ?? null;
42649
42775
  return /* @__PURE__ */ jsxs("div", { css: /* @__PURE__ */ css$1({
42650
42776
  ...css2.container,
42651
42777
  ...selected && css2.containerSelected,
@@ -43353,7 +43479,6 @@ function DetailAccordion({
43353
43479
  }) });
43354
43480
  }
43355
43481
  const AXIS_LOCK_PX = 8;
43356
- const ROTATE_STEP_PX = 50;
43357
43482
  function ZoomModal({
43358
43483
  frameUrls,
43359
43484
  selectedFrameIndex,
@@ -43399,15 +43524,7 @@ function ZoomModal({
43399
43524
  if (mode === "scroll" && scrollArea) {
43400
43525
  scrollArea.scrollTop = startScrollTop - deltaY;
43401
43526
  } else if (mode === "rotate") {
43402
- const rotateDelta = move.clientX - lastRotateX;
43403
- if (Math.abs(rotateDelta) >= ROTATE_STEP_PX) {
43404
- if (rotateDelta > 0) {
43405
- rotateRight();
43406
- } else {
43407
- rotateLeft();
43408
- }
43409
- lastRotateX = move.clientX;
43410
- }
43527
+ lastRotateX += applyDragSteps(move.clientX - lastRotateX, rotateLeft, rotateRight);
43411
43528
  }
43412
43529
  };
43413
43530
  const onUp = () => {
@@ -43453,7 +43570,11 @@ function ZoomModal({
43453
43570
  transform: "translateY(-50%)",
43454
43571
  display: "flex",
43455
43572
  cursor: "pointer",
43456
- zIndex: 1
43573
+ zIndex: 1,
43574
+ // Rapid clicks shouldn't initiate a text-selection drag into the
43575
+ // close-button glyph or any other overlay text.
43576
+ userSelect: "none",
43577
+ WebkitUserSelect: "none"
43457
43578
  },
43458
43579
  chevronLeft: {
43459
43580
  left: "8px"
@@ -43499,11 +43620,11 @@ function ZoomModal({
43499
43620
  /* @__PURE__ */ jsx$1("div", { css: /* @__PURE__ */ css$1({
43500
43621
  ...css2.chevron,
43501
43622
  ...css2.chevronLeft
43502
- }, "", ""), onClick: rotateLeft, children: /* @__PURE__ */ jsx$1(SvgChevronLeft, { css: css2.chevronIcon }) }),
43623
+ }, "", ""), onMouseDown: (e) => e.preventDefault(), onClick: rotateLeft, children: /* @__PURE__ */ jsx$1(SvgChevronLeft, { css: css2.chevronIcon }) }),
43503
43624
  /* @__PURE__ */ jsx$1("div", { css: /* @__PURE__ */ css$1({
43504
43625
  ...css2.chevron,
43505
43626
  ...css2.chevronRight
43506
- }, "", ""), onClick: rotateRight, children: /* @__PURE__ */ jsx$1(SvgChevronRight, { css: css2.chevronIcon }) }),
43627
+ }, "", ""), onMouseDown: (e) => e.preventDefault(), onClick: rotateRight, children: /* @__PURE__ */ jsx$1(SvgChevronRight, { css: css2.chevronIcon }) }),
43507
43628
  /* @__PURE__ */ jsx$1("button", { css: css2.close, onClick: onClose, "aria-label": "Close zoom", children: "×" })
43508
43629
  ] });
43509
43630
  }
@@ -43523,6 +43644,7 @@ function DesktopLayout$1({
43523
43644
  forceUntuck,
43524
43645
  canTuck,
43525
43646
  frameUrls,
43647
+ autoRotateTrigger,
43526
43648
  onSelectItem,
43527
43649
  onRemoveItem,
43528
43650
  onOpenAccordionItem,
@@ -43624,7 +43746,7 @@ function DesktopLayout$1({
43624
43746
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, css: css2.container, style: {
43625
43747
  gridTemplateColumns
43626
43748
  }, children: [
43627
- /* @__PURE__ */ jsx$1("div", { css: css2.avatarColumn, onMouseEnter: () => setAvatarHovered(true), onMouseLeave: () => setAvatarHovered(false), children: /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection, frameUrls, controls, selectedFrameIndex, setSelectedFrameIndex }) }),
43749
+ /* @__PURE__ */ jsx$1("div", { css: css2.avatarColumn, onMouseEnter: () => setAvatarHovered(true), onMouseLeave: () => setAvatarHovered(false), children: /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection, frameUrls, controls, selectedFrameIndex, setSelectedFrameIndex, autoRotateTrigger }) }),
43628
43750
  hasSelection ? /* @__PURE__ */ jsx$1("div", { css: css2.detailColumn, children: /* @__PURE__ */ jsx$1(DetailAccordion, { items: selectedItems, openItemExternalId: openAccordionItemId, platform: "desktop", detailMode, isMobileQuickRow: false, forceUntuck, canTuck, onOpenItem: onOpenAccordionItem, onChangeDetailMode, onChangeSize, onChangeColor, onAddToCart, onToggleUntuck }) }) : null,
43629
43751
  /* @__PURE__ */ jsxs("div", { css: css2.railsColumn, children: [
43630
43752
  /* @__PURE__ */ jsxs("span", { css: css2.signOutWrapper, onClick: onSignOut, children: [
@@ -43751,6 +43873,7 @@ function MobileLayout$1({
43751
43873
  forceUntuck,
43752
43874
  canTuck,
43753
43875
  frameUrls,
43876
+ autoRotateTrigger,
43754
43877
  sheetSnap,
43755
43878
  sheetTouchStart,
43756
43879
  onSelectItem,
@@ -43767,7 +43890,7 @@ function MobileLayout$1({
43767
43890
  if (mode === "browse") {
43768
43891
  return /* @__PURE__ */ jsx$1(BrowseView, { resolved, availabilityByExternalId, selectedCount: selectedItems.length, onSelectItem, onRemoveItem, onTryItOn });
43769
43892
  }
43770
- return /* @__PURE__ */ jsx$1(TryOnView, { selectedItems, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, sheetSnap, sheetTouchStart, onBackToBrowse, onOpenAccordionItem, onChangeDetailMode, onChangeSize, onChangeColor, onAddToCart, onToggleUntuck });
43893
+ return /* @__PURE__ */ jsx$1(TryOnView, { selectedItems, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, autoRotateTrigger, sheetSnap, sheetTouchStart, onBackToBrowse, onOpenAccordionItem, onChangeDetailMode, onChangeSize, onChangeColor, onAddToCart, onToggleUntuck });
43771
43894
  }
43772
43895
  function BrowseView({
43773
43896
  resolved,
@@ -43861,6 +43984,7 @@ function TryOnView({
43861
43984
  forceUntuck,
43862
43985
  canTuck,
43863
43986
  frameUrls,
43987
+ autoRotateTrigger,
43864
43988
  sheetSnap,
43865
43989
  sheetTouchStart,
43866
43990
  onBackToBrowse,
@@ -43961,7 +44085,7 @@ function TryOnView({
43961
44085
  }
43962
44086
  }));
43963
44087
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
43964
- /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection: selectedItems.length > 0, frameUrls, mobileFullscreen: true, controls: /* @__PURE__ */ jsx$1(MobileTuckControl, { canTuck, forceUntuck, onToggleUntuck }) }),
44088
+ /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection: selectedItems.length > 0, frameUrls, autoRotateTrigger, mobileFullscreen: true, controls: /* @__PURE__ */ jsx$1(MobileTuckControl, { canTuck, forceUntuck, onToggleUntuck }) }),
43965
44089
  /* @__PURE__ */ jsx$1(Button, { variant: "base", css: css2.backButton, onClick: onBackToBrowse, "aria-label": "Back to browse", children: /* @__PURE__ */ jsx$1(SvgIconLeftArrow, { css: css2.backArrow }) }),
43966
44090
  /* @__PURE__ */ jsx$1("div", { css: css2.sheetOuter, style: sheetStyle, children: /* @__PURE__ */ jsxs("div", { ref: innerRef, css: css2.sheetInner, style: sheetStyle, children: [
43967
44091
  /* @__PURE__ */ jsxs("div", { css: css2.sheetHandleRow, onTouchStart: sheetTouchStart, children: [
@@ -44106,9 +44230,11 @@ function FittingRoomOverlay({
44106
44230
  const closeOverlay = useMainStore((state) => state.closeOverlay);
44107
44231
  const openOverlay = useMainStore((state) => state.openOverlay);
44108
44232
  const updateFittingRoomItem = useMainStore((state) => state.updateFittingRoomItem);
44233
+ const removeFromFittingRoom = useMainStore((state) => state.removeFromFittingRoom);
44109
44234
  const resolved = useResolvedFittingRoom();
44110
44235
  const [topOffset, setTopOffset] = reactExports.useState(0);
44111
44236
  const [selectedExternalIds, setSelectedExternalIds] = reactExports.useState(() => /* @__PURE__ */ new Set());
44237
+ const [autoRotateTrigger, setAutoRotateTrigger] = reactExports.useState(void 0);
44112
44238
  const [openAccordionItemId, setOpenAccordionItemId] = reactExports.useState(null);
44113
44239
  const [detailMode, setDetailMode] = reactExports.useState("compact");
44114
44240
  const [forceUntuck, setForceUntuck] = reactExports.useState(false);
@@ -44215,6 +44341,7 @@ function FittingRoomOverlay({
44215
44341
  nextSelected.add(externalId);
44216
44342
  ensureSizeForItem(item);
44217
44343
  setLastAddedExternalId(externalId);
44344
+ setAutoRotateTrigger((n) => (n ?? 0) + 1);
44218
44345
  if (!isMobileLayout) {
44219
44346
  setOpenAccordionItemId(externalId);
44220
44347
  setDetailMode("compact");
@@ -44307,7 +44434,8 @@ function FittingRoomOverlay({
44307
44434
  if (openAccordionItemId === externalId) {
44308
44435
  setOpenAccordionItemId(null);
44309
44436
  }
44310
- }, [openAccordionItemId]);
44437
+ removeFromFittingRoom(externalId);
44438
+ }, [openAccordionItemId, removeFromFittingRoom]);
44311
44439
  const handleTryItOn = reactExports.useCallback(() => {
44312
44440
  setMobileMode("try-on");
44313
44441
  setSheetSnap("collapsed");
@@ -44325,7 +44453,8 @@ function FittingRoomOverlay({
44325
44453
  if (preselectAppliedRef.current || !preselectExternalId) {
44326
44454
  return;
44327
44455
  }
44328
- if (!resolved.items.some((i) => i.externalId === preselectExternalId)) {
44456
+ const item = resolved.items.find((i) => i.externalId === preselectExternalId);
44457
+ if (!item || !item.merchantProduct || !item.loadedProduct) {
44329
44458
  return;
44330
44459
  }
44331
44460
  preselectAppliedRef.current = true;
@@ -44452,7 +44581,7 @@ function FittingRoomOverlay({
44452
44581
  /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.emptyTagline, t: "landing.description" }),
44453
44582
  /* @__PURE__ */ jsx$1("div", { css: css2.emptyShopNow, children: /* @__PURE__ */ jsx$1(ButtonT, { variant: "primary", t: "fitting_room.shop_now", onClick: handleShopNow }) }),
44454
44583
  userIsLoggedIn ? /* @__PURE__ */ jsx$1(LinkT, { variant: "underline", css: css2.emptySignOut, t: "fitting_room.sign_out", onClick: handleSignOut }) : null
44455
- ] }) }) : isMobileLayout ? /* @__PURE__ */ jsx$1(MobileLayout$1, { mode: mobileMode, resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, sheetSnap, sheetTouchStart, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onTryItOn: handleTryItOn, onBackToBrowse: handleBackToBrowse, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onChangeColor: handleChangeColor, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck }) : /* @__PURE__ */ jsx$1(DesktopLayout$1, { resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onChangeColor: handleChangeColor, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck, onSignOut: handleSignOut }),
44584
+ ] }) }) : isMobileLayout ? /* @__PURE__ */ jsx$1(MobileLayout$1, { mode: mobileMode, resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, autoRotateTrigger, sheetSnap, sheetTouchStart, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onTryItOn: handleTryItOn, onBackToBrowse: handleBackToBrowse, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onChangeColor: handleChangeColor, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck }) : /* @__PURE__ */ jsx$1(DesktopLayout$1, { resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, autoRotateTrigger, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onChangeColor: handleChangeColor, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck, onSignOut: handleSignOut }),
44456
44585
  vtoError ? /* @__PURE__ */ jsx$1(Snackbar, { messageKey: "fitting_room.vto_error", onDismiss: clearVtoError }) : null
44457
44586
  ] }) });
44458
44587
  }
@@ -45179,6 +45308,8 @@ function FitChart({
45179
45308
  const AVATAR_IMAGE_ASPECT_RATIO = 2 / 3;
45180
45309
  const AVATAR_GUTTER_HEIGHT_PX = 100;
45181
45310
  const CONTENT_AREA_WIDTH_PX = 550;
45311
+ const SHOW_ROTATION_SLIDER = false;
45312
+ const DESKTOP_AVATAR_GUTTER_HEIGHT_PX = 0;
45182
45313
  const logger$5 = getLogger("overlays/quick-view");
45183
45314
  function QuickViewOverlay() {
45184
45315
  const userIsLoggedIn = useMainStore((state) => state.userIsLoggedIn);
@@ -45188,6 +45319,8 @@ function QuickViewOverlay() {
45188
45319
  const deviceLayout = useMainStore((state) => state.deviceLayout);
45189
45320
  const openOverlay = useMainStore((state) => state.openOverlay);
45190
45321
  const closeOverlay = useMainStore((state) => state.closeOverlay);
45322
+ const updateFittingRoomItem = useMainStore((state) => state.updateFittingRoomItem);
45323
+ const fittingRoomItems = useMainStore((state) => state.fittingRoom);
45191
45324
  const [vtoProductData, setVtoProductData] = reactExports.useState(null);
45192
45325
  const [selectedSizeLabel, setSelectedSizeLabel] = reactExports.useState(null);
45193
45326
  const [selectedColorLabel, setSelectedColorLabel] = reactExports.useState(null);
@@ -45422,6 +45555,27 @@ function QuickViewOverlay() {
45422
45555
  });
45423
45556
  });
45424
45557
  }, [closeOverlay]);
45558
+ const handleChangeColor = reactExports.useCallback((newColorLabel) => {
45559
+ setSelectedColorLabel(newColorLabel);
45560
+ const currentProduct = getStaticData().currentProduct;
45561
+ if (!currentProduct) {
45562
+ return;
45563
+ }
45564
+ const inRoom = fittingRoomItems.some((item) => item.externalId === currentProduct.externalId);
45565
+ if (!inRoom || !vtoProductData || !selectedSizeLabel) {
45566
+ return;
45567
+ }
45568
+ const sizeRec = vtoProductData.sizes.find((s) => s.sizeLabel === selectedSizeLabel);
45569
+ const csa = sizeRec?.colors.find((c) => c.colorLabel === newColorLabel);
45570
+ if (!csa) {
45571
+ return;
45572
+ }
45573
+ updateFittingRoomItem(currentProduct.externalId, {
45574
+ colorwaySizeAssetId: csa.colorwaySizeAssetId,
45575
+ size: selectedSizeLabel,
45576
+ color: csa.colorLabel
45577
+ });
45578
+ }, [fittingRoomItems, selectedSizeLabel, updateFittingRoomItem, vtoProductData]);
45425
45579
  const handleAddToCartClick = reactExports.useCallback(async () => {
45426
45580
  try {
45427
45581
  if (!selectedSizeLabel) {
@@ -45457,7 +45611,7 @@ function QuickViewOverlay() {
45457
45611
  Layout = DesktopLayout;
45458
45612
  }
45459
45613
  return /* @__PURE__ */ jsxs(SidecarModalFrame, { onRequestClose: closeOverlay, contentStyle: modalStyle, children: [
45460
- /* @__PURE__ */ jsx$1(Layout, { loadedProductData: vtoProductData, selectedColorSizeRecord, availableColorLabels, selectedColorLabel, selectedSizeLabel, frameUrls, setModalStyle, onClose: closeOverlay, onChangeColor: setSelectedColorLabel, onChangeSize: setSelectedSizeLabel, onAddToCart: handleAddToCartClick, onSignOut: handleSignOutClick }),
45614
+ /* @__PURE__ */ jsx$1(Layout, { loadedProductData: vtoProductData, selectedColorSizeRecord, availableColorLabels, selectedColorLabel, selectedSizeLabel, frameUrls, setModalStyle, onClose: closeOverlay, onChangeColor: handleChangeColor, onChangeSize: setSelectedSizeLabel, onAddToCart: handleAddToCartClick, onSignOut: handleSignOutClick }),
45461
45615
  vtoError ? /* @__PURE__ */ jsx$1(Snackbar, { messageKey: "quick-view.vto_error", onDismiss: clearVtoError }) : null
45462
45616
  ] });
45463
45617
  }
@@ -45695,7 +45849,10 @@ function MobileLayout({
45695
45849
  }
45696
45850
  function MobileContentCollapsed({
45697
45851
  loadedProductData,
45852
+ availableColorLabels,
45853
+ selectedColorLabel,
45698
45854
  selectedSizeLabel,
45855
+ onChangeColor,
45699
45856
  onChangeSize
45700
45857
  }) {
45701
45858
  const css2 = useCss((_theme) => ({
@@ -45705,17 +45862,24 @@ function MobileContentCollapsed({
45705
45862
  selectSizeLabelText: {},
45706
45863
  sizeSelectorContainer: {
45707
45864
  marginTop: "8px"
45865
+ },
45866
+ colorSelectorContainer: {
45867
+ marginTop: "8px"
45708
45868
  }
45709
45869
  }));
45710
45870
  return /* @__PURE__ */ jsxs(Fragment, { children: [
45711
45871
  /* @__PURE__ */ jsx$1("div", { css: css2.selectSizeLabelContainer, children: /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.selectSizeLabelText, t: "size-rec.select_size" }) }),
45712
- /* @__PURE__ */ jsx$1("div", { css: css2.sizeSelectorContainer, children: /* @__PURE__ */ jsx$1(SizeSelector, { loadedProductData, selectedSizeLabel, onChangeSize }) })
45872
+ /* @__PURE__ */ jsx$1("div", { css: css2.sizeSelectorContainer, children: /* @__PURE__ */ jsx$1(SizeSelector, { loadedProductData, selectedSizeLabel, onChangeSize }) }),
45873
+ /* @__PURE__ */ jsx$1("div", { css: css2.colorSelectorContainer, children: /* @__PURE__ */ jsx$1(ColorSelector, { availableColorLabels, selectedColorLabel, onChangeColor }) })
45713
45874
  ] });
45714
45875
  }
45715
45876
  function MobileContentExpanded({
45716
45877
  loadedProductData,
45878
+ availableColorLabels,
45879
+ selectedColorLabel,
45717
45880
  selectedSizeLabel,
45718
45881
  onChangeContentView,
45882
+ onChangeColor,
45719
45883
  onChangeSize,
45720
45884
  onAddToCart
45721
45885
  }) {
@@ -45727,6 +45891,9 @@ function MobileContentExpanded({
45727
45891
  sizeSelectorContainer: {
45728
45892
  marginTop: "8px"
45729
45893
  },
45894
+ colorSelectorContainer: {
45895
+ marginTop: "8px"
45896
+ },
45730
45897
  itemFitTextContainer: {
45731
45898
  marginTop: "8px"
45732
45899
  },
@@ -45756,6 +45923,7 @@ function MobileContentExpanded({
45756
45923
  return /* @__PURE__ */ jsxs(Fragment, { children: [
45757
45924
  /* @__PURE__ */ jsx$1("div", { css: css2.selectSizeLabelContainer, children: /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.selectSizeLabelText, t: "size-rec.select_size" }) }),
45758
45925
  /* @__PURE__ */ jsx$1("div", { css: css2.sizeSelectorContainer, children: /* @__PURE__ */ jsx$1(SizeSelector, { loadedProductData, selectedSizeLabel, onChangeSize }) }),
45926
+ /* @__PURE__ */ jsx$1("div", { css: css2.colorSelectorContainer, children: /* @__PURE__ */ jsx$1(ColorSelector, { availableColorLabels, selectedColorLabel, onChangeColor }) }),
45759
45927
  /* @__PURE__ */ jsx$1("div", { css: css2.itemFitTextContainer, children: /* @__PURE__ */ jsx$1(ItemFitText, { loadedProductData }) }),
45760
45928
  /* @__PURE__ */ jsx$1("div", { css: css2.itemFitDetailsContainer, children: /* @__PURE__ */ jsx$1(ItemFitDetails, { loadedProductData, selectedSizeLabel }) }),
45761
45929
  /* @__PURE__ */ jsxs("div", { css: css2.buttonContainer, children: [
@@ -45909,7 +46077,7 @@ function DesktopLayout({
45909
46077
  productNameContainer: {},
45910
46078
  productNameText: {
45911
46079
  fontFamily: "'Inter', sans-serif",
45912
- fontSize: "32px",
46080
+ fontSize: "24px",
45913
46081
  fontWeight: 300
45914
46082
  },
45915
46083
  priceContainer: {
@@ -46029,6 +46197,7 @@ function Avatar({
46029
46197
  });
46030
46198
  const [selectedFrameIndex, setSelectedFrameIndex] = reactExports.useState(null);
46031
46199
  const [zoomOpen, setZoomOpen] = reactExports.useState(false);
46200
+ const cancelAutoRotate = useAutoRotate(1, frameUrls, selectedFrameIndex, setSelectedFrameIndex);
46032
46201
  const css2 = useCss((theme) => ({
46033
46202
  topContainer: {
46034
46203
  flex: "none",
@@ -46038,8 +46207,11 @@ function Avatar({
46038
46207
  },
46039
46208
  zoomPill: {
46040
46209
  position: "absolute",
46041
- // Bottom-right of the avatar image, clear of the slider gutter below it.
46042
- bottom: `${AVATAR_GUTTER_HEIGHT_PX + 16}px`,
46210
+ // Bottom-right of the avatar image, 16px above the image's bottom edge.
46211
+ // When the rotation slider is on, that's also `clear of the gutter` —
46212
+ // when the slider is hidden, the gutter collapses to 0 and the 16px
46213
+ // offset alone keeps the pill inset from the image's true bottom.
46214
+ bottom: `${DESKTOP_AVATAR_GUTTER_HEIGHT_PX + 16}px`,
46043
46215
  right: "16px",
46044
46216
  display: "inline-flex",
46045
46217
  alignItems: "center",
@@ -46053,6 +46225,10 @@ function Avatar({
46053
46225
  letterSpacing: "0.5px",
46054
46226
  textTransform: "uppercase",
46055
46227
  cursor: "pointer",
46228
+ // Rapid clicks on the rotation chevrons can otherwise spill a triple-
46229
+ // click selection into this label.
46230
+ userSelect: "none",
46231
+ WebkitUserSelect: "none",
46056
46232
  zIndex: 2
46057
46233
  },
46058
46234
  zoomPillIcon: {
@@ -46129,7 +46305,7 @@ function Avatar({
46129
46305
  }
46130
46306
  } else {
46131
46307
  const screenHeightPx = window.innerHeight;
46132
- const bottomContainerHeightPx = AVATAR_GUTTER_HEIGHT_PX;
46308
+ const bottomContainerHeightPx = DESKTOP_AVATAR_GUTTER_HEIGHT_PX;
46133
46309
  const imageHeightPx = screenHeightPx - bottomContainerHeightPx;
46134
46310
  const imageWidthPx = Math.floor(imageHeightPx * AVATAR_IMAGE_ASPECT_RATIO);
46135
46311
  const modalWidthPx = imageWidthPx + CONTENT_AREA_WIDTH_PX;
@@ -46172,13 +46348,13 @@ function Avatar({
46172
46348
  }, [isMobileLayout, setModalStyle]);
46173
46349
  const isReady = !!frameUrls && selectedFrameIndex != null;
46174
46350
  return /* @__PURE__ */ jsxs("div", { css: css2.topContainer, style: layoutData.topContainerStyle, children: [
46175
- /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: layoutData.imageContainerStyle, imageStyle: layoutData.imageStyle, loadingT: "quick-view.avatar_loading" }),
46351
+ /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: layoutData.imageContainerStyle, imageStyle: layoutData.imageStyle, loadingT: "quick-view.avatar_loading", onUserInteract: cancelAutoRotate }),
46176
46352
  isReady && !isMobileLayout ? /* @__PURE__ */ jsxs(Button, { variant: "base", css: css2.zoomPill, onClick: () => setZoomOpen(true), children: [
46177
46353
  /* @__PURE__ */ jsx$1(SvgIconZoom, { css: css2.zoomPillIcon }),
46178
46354
  /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "quick-view.zoom_in" })
46179
46355
  ] }) : null,
46180
46356
  zoomOpen && frameUrls && frameUrls.length > 0 ? /* @__PURE__ */ jsx$1(ZoomModal, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, onClose: () => setZoomOpen(false) }) : null,
46181
- frameUrls && selectedFrameIndex != null ? /* @__PURE__ */ jsx$1("div", { css: css2.bottomContainer, style: layoutData.bottomContainerStyle, children: isMobileLayout ? /* @__PURE__ */ jsx$1(Fragment, { children: " " }) : /* @__PURE__ */ jsxs(Fragment, { children: [
46357
+ frameUrls && selectedFrameIndex != null && (isMobileLayout || SHOW_ROTATION_SLIDER) ? /* @__PURE__ */ jsx$1("div", { css: css2.bottomContainer, style: layoutData.bottomContainerStyle, children: isMobileLayout ? /* @__PURE__ */ jsx$1(Fragment, { children: " " }) : /* @__PURE__ */ jsxs(Fragment, { children: [
46182
46358
  /* @__PURE__ */ jsx$1("input", { type: "range", min: 0, max: frameUrls.length - 1, step: 1, value: selectedFrameIndex, onChange: (e) => setSelectedFrameIndex(Number(e.target.value)), css: css2.sliderInput }),
46183
46359
  /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "quick-view.slide_to_rotate", css: css2.sliderText })
46184
46360
  ] }) }) : null
@@ -46706,28 +46882,41 @@ const useMainStore = create((set) => ({
46706
46882
  }
46707
46883
  })),
46708
46884
  // Fitting room:
46885
+ //
46886
+ // Each mutation reads the latest localStorage state before applying the
46887
+ // change, so two tabs adding different products at the same time merge
46888
+ // instead of last-write-wins. The in-memory Zustand value is just a
46889
+ // cache of "what was in localStorage the last time we touched it".
46890
+ // Cross-tab UI freshness (Tab B's open fitting-room sees Tab A's add
46891
+ // without a mutation of its own) is handled by the `storage` event
46892
+ // listener registered in fitting-room-storage.ts::_init.
46709
46893
  fittingRoom: [],
46710
- addToFittingRoom: (item) => set((prevState) => {
46711
- const filtered = prevState.fittingRoom.filter((existing) => existing.externalId !== item.externalId);
46712
- const next2 = [...filtered, item];
46713
- writeFittingRoom(getStaticData().brandId, next2);
46894
+ addToFittingRoom: (item) => set(() => {
46895
+ const brandId = getStaticData().brandId;
46896
+ const fresh = readFittingRoom(brandId);
46897
+ const next2 = [...fresh.filter((existing) => existing.externalId !== item.externalId), item];
46898
+ writeFittingRoom(brandId, next2);
46714
46899
  return {
46715
46900
  fittingRoom: next2
46716
46901
  };
46717
46902
  }),
46718
- removeFromFittingRoom: (externalId) => set((prevState) => {
46719
- const next2 = prevState.fittingRoom.filter((existing) => existing.externalId !== externalId);
46720
- writeFittingRoom(getStaticData().brandId, next2);
46903
+ removeFromFittingRoom: (externalId) => set(() => {
46904
+ const brandId = getStaticData().brandId;
46905
+ const fresh = readFittingRoom(brandId);
46906
+ const next2 = fresh.filter((existing) => existing.externalId !== externalId);
46907
+ writeFittingRoom(brandId, next2);
46721
46908
  return {
46722
46909
  fittingRoom: next2
46723
46910
  };
46724
46911
  }),
46725
- updateFittingRoomItem: (externalId, patch) => set((prevState) => {
46726
- const next2 = prevState.fittingRoom.map((existing) => existing.externalId === externalId ? {
46912
+ updateFittingRoomItem: (externalId, patch) => set(() => {
46913
+ const brandId = getStaticData().brandId;
46914
+ const fresh = readFittingRoom(brandId);
46915
+ const next2 = fresh.map((existing) => existing.externalId === externalId ? {
46727
46916
  ...existing,
46728
46917
  ...patch
46729
46918
  } : existing);
46730
- writeFittingRoom(getStaticData().brandId, next2);
46919
+ writeFittingRoom(brandId, next2);
46731
46920
  return {
46732
46921
  fittingRoom: next2
46733
46922
  };
@@ -46794,9 +46983,9 @@ const SHARED_CONFIG = {
46794
46983
  appGooglePlayUrl: "https://play.google.com/store/apps/details?id=com.thefittingroom.marketplace"
46795
46984
  },
46796
46985
  build: {
46797
- version: `${"5.0.26"}`,
46798
- commitHash: `${"357d81c"}`,
46799
- date: `${"2026-05-23T18:08:09.620Z"}`
46986
+ version: `${"5.0.27"}`,
46987
+ commitHash: `${"9e3e717"}`,
46988
+ date: `${"2026-05-24T21:00:26.048Z"}`
46800
46989
  }
46801
46990
  };
46802
46991
  const CONFIGS = {
@@ -47016,10 +47205,12 @@ async function logout() {
47016
47205
  }
47017
47206
  const TFR = {
47018
47207
  init,
47019
- logout
47208
+ logout,
47209
+ syncCurrentProductSelection
47020
47210
  };
47021
47211
  export {
47022
47212
  TFR as default,
47023
47213
  init,
47024
- logout
47214
+ logout,
47215
+ syncCurrentProductSelection
47025
47216
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thefittingroom/shop-ui",
3
- "version": "5.0.26",
3
+ "version": "5.0.27",
4
4
  "description": "the fitting room UI library",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",