@wewear/virtual-try-on 1.2.0 → 1.3.1

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/index.js CHANGED
@@ -145,44 +145,117 @@
145
145
  });
146
146
  }
147
147
 
148
- function createButton(buttonPosition, onClick) {
148
+ function createButtonContainer(buttonPosition, hasVirtualTryOn = false, onCameraClick, onRefreshClick, onToggleClick, isShowingVirtualTryOn = false) {
149
149
  const container = document.createElement("div");
150
- container.className = CSS_CLASSES.BUTTON_CONTAINER;
150
+ container.className = `${CSS_CLASSES.BUTTON_CONTAINER} ww-button-group`;
151
151
  const positionStyles = getPositionStyles(buttonPosition);
152
152
  container.style.cssText = `
153
153
  position: absolute;
154
154
  ${positionStyles}
155
155
  display: flex;
156
- border-radius: 50%;
157
- background: #000000;
156
+ border-radius: 9999px;
157
+ background: white;
158
158
  padding: 4px;
159
159
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
160
160
  z-index: ${Z_INDEX.BUTTON};
161
161
  `;
162
- const button = document.createElement("button");
163
- button.type = "button";
164
- button.className = CSS_CLASSES.BUTTON;
165
- button.setAttribute("aria-label", "Virtual Try-On");
166
- button.style.cssText = `
162
+ // Camera button
163
+ const cameraButton = document.createElement("button");
164
+ cameraButton.type = "button";
165
+ cameraButton.className = `${CSS_CLASSES.BUTTON} ww-camera-btn`;
166
+ cameraButton.setAttribute("aria-label", "Virtual Try-On");
167
+ cameraButton.style.cssText = `
167
168
  display: flex;
168
169
  width: 36px;
169
170
  height: 36px;
170
171
  cursor: pointer;
171
172
  align-items: center;
172
173
  justify-content: center;
173
- border-radius: 50%;
174
- padding: 10px;
174
+ border-radius: 9999px;
175
+ padding: 8px;
175
176
  border: none;
176
177
  background: transparent;
177
- color: #ffffff;
178
+ color: currentColor;
178
179
  `;
179
- button.innerHTML = `
180
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-camera-icon lucide-camera"><path d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"/><circle cx="12" cy="13" r="3"/></svg>
180
+ cameraButton.innerHTML = `
181
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-camera">
182
+ <path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"></path>
183
+ <circle cx="12" cy="13" r="3"></circle>
184
+ </svg>
181
185
  `;
182
- button.onclick = onClick;
183
- container.appendChild(button);
186
+ cameraButton.onclick = onCameraClick;
187
+ container.appendChild(cameraButton);
188
+ // Add refresh and toggle buttons if we have a virtual try-on
189
+ if (hasVirtualTryOn) {
190
+ // Refresh button - calls API again
191
+ if (onRefreshClick) {
192
+ const refreshButton = document.createElement("button");
193
+ refreshButton.type = "button";
194
+ refreshButton.className = "ww-refresh-btn";
195
+ refreshButton.setAttribute("aria-label", "Refresh virtual try-on");
196
+ refreshButton.style.cssText = `
197
+ display: flex;
198
+ width: 36px;
199
+ height: 36px;
200
+ cursor: pointer;
201
+ align-items: center;
202
+ justify-content: center;
203
+ border-radius: 9999px;
204
+ padding: 8px;
205
+ border: none;
206
+ background: transparent;
207
+ color: currentColor;
208
+ `;
209
+ refreshButton.innerHTML = `
210
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-refresh-ccw">
211
+ <path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
212
+ <path d="M3 3v5h5"></path>
213
+ <path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"></path>
214
+ <path d="M16 16h5v5"></path>
215
+ </svg>
216
+ `;
217
+ refreshButton.onclick = onRefreshClick;
218
+ container.appendChild(refreshButton);
219
+ }
220
+ // Toggle button (scan-face) - switches between original and virtual try-on
221
+ if (onToggleClick) {
222
+ const toggleButton = document.createElement("button");
223
+ toggleButton.type = "button";
224
+ toggleButton.className = "ww-toggle-btn";
225
+ toggleButton.setAttribute("aria-label", isShowingVirtualTryOn ? "Show Original Image" : "Show Virtual Try-On");
226
+ toggleButton.style.cssText = `
227
+ display: flex;
228
+ width: 36px;
229
+ height: 36px;
230
+ cursor: pointer;
231
+ align-items: center;
232
+ justify-content: center;
233
+ border-radius: 9999px;
234
+ padding: 8px;
235
+ border: none;
236
+ background: ${isShowingVirtualTryOn ? "rgba(0, 0, 0)" : "none"};
237
+ color: ${isShowingVirtualTryOn ? "white" : "currentColor"};
238
+ `;
239
+ toggleButton.innerHTML = `
240
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scan-face">
241
+ <path d="M3 7V5a2 2 0 0 1 2-2h2"></path>
242
+ <path d="M17 3h2a2 2 0 0 1 2 2v2"></path>
243
+ <path d="M21 17v2a2 2 0 0 1-2 2h-2"></path>
244
+ <path d="M7 21H5a2 2 0 0 1-2-2v-2"></path>
245
+ <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
246
+ <path d="M9 9h.01"></path>
247
+ <path d="M15 9h.01"></path>
248
+ </svg>
249
+ `;
250
+ toggleButton.onclick = onToggleClick;
251
+ container.appendChild(toggleButton);
252
+ }
253
+ }
184
254
  return container;
185
255
  }
256
+ function createButton(buttonPosition, onClick) {
257
+ return createButtonContainer(buttonPosition, false, onClick);
258
+ }
186
259
 
187
260
  function showCameraModal(callbacks) {
188
261
  console.log("[WeWear VTO] Opening camera modal...");
@@ -306,92 +379,104 @@
306
379
  startCamera(video, loadingIndicator, captureButton);
307
380
  }
308
381
 
309
- function showImageModal(imageUrl) {
310
- // Remove any existing modals first
311
- removeElements(`.${CSS_CLASSES.MODAL}`);
312
- // Create modal container
313
- const modal = document.createElement("div");
314
- modal.className = CSS_CLASSES.MODAL;
315
- modal.style.cssText = `
316
- position: fixed;
382
+ /**
383
+ * Creates a loading overlay that can be shown in different containers
384
+ */
385
+ function createLoadingOverlay(text = "Processing...") {
386
+ const overlay = document.createElement("div");
387
+ overlay.className = "ww-loading-overlay";
388
+ overlay.style.cssText = `
389
+ position: absolute;
317
390
  top: 0;
318
391
  left: 0;
319
392
  width: 100%;
320
393
  height: 100%;
321
- background-color: rgba(0, 0, 0, 0.9);
322
- display: flex;
323
- align-items: center;
324
- justify-content: center;
325
- z-index: ${Z_INDEX.MODAL};
326
- padding: 20px;
327
- box-sizing: border-box;
328
- `;
329
- // Create image container
330
- const imageContainer = document.createElement("div");
331
- imageContainer.style.cssText = `
332
- position: relative;
333
- width: 100%;
334
- max-width: 500px;
335
- height: 70vh;
336
- background-color: #000;
337
- border-radius: 12px;
338
- overflow: hidden;
394
+ background-color: rgba(0, 0, 0, 0.8);
339
395
  display: flex;
340
396
  flex-direction: column;
341
397
  align-items: center;
342
398
  justify-content: center;
399
+ z-index: ${Z_INDEX.MODAL + 1};
400
+ border-radius: inherit;
343
401
  `;
344
- // Create image element
345
- const image = document.createElement("img");
346
- image.src = imageUrl;
347
- image.alt = "Virtual Try-On Result";
348
- image.style.cssText = `
349
- width: 100%;
350
- height: 100%;
351
- object-fit: cover;
352
- border-radius: 12px;
353
- `;
354
- // Create close button
355
- const closeButton = document.createElement("button");
356
- closeButton.innerHTML = "×";
357
- closeButton.style.cssText = `
358
- position: absolute;
359
- top: 15px;
360
- right: 15px;
361
- width: 40px;
362
- height: 40px;
363
- border: none;
364
- background-color: rgba(0, 0, 0, 0.7);
365
- color: white;
366
- font-size: 24px;
367
- font-weight: bold;
368
- cursor: pointer;
369
- border-radius: 50%;
370
- display: flex;
371
- align-items: center;
372
- justify-content: center;
402
+ // Add CSS animation to the document if not already added
403
+ if (!document.getElementById("ww-loading-animation")) {
404
+ const style = document.createElement("style");
405
+ style.id = "ww-loading-animation";
406
+ style.textContent = `
407
+ @keyframes ww-pulse {
408
+ 0%, 100% { opacity: 0.7; transform: scale(1); }
409
+ 50% { opacity: 1; transform: scale(1.05); }
410
+ }
411
+ .ww-loading-overlay .ww-logo {
412
+ animation: ww-pulse 2s ease-in-out infinite;
413
+ }
414
+ `;
415
+ document.head.appendChild(style);
416
+ }
417
+ overlay.innerHTML = `
418
+ <div style="display: flex; flex-direction: column; align-items: center; gap: 24px; color: white;">
419
+ <svg class="ww-logo" width="80" height="50" viewBox="0 0 214 135" fill="none" xmlns="http://www.w3.org/2000/svg">
420
+ <g clip-path="url(#clip0_3624_12755)">
421
+ <path d="M102.906 74.8679C102.906 77.9717 101.574 80.7453 98.9104 83.1887C96.6871 85.1918 93.9025 86.6997 90.5566 87.7123C87.695 88.5708 84.6462 89 81.4104 89C73.8821 89 68.0047 87.0189 63.7783 83.0566C59.5519 87.0189 53.6855 89 46.1792 89C42.9434 89 39.9057 88.5708 37.066 87.7123C33.7201 86.6997 30.9245 85.1918 28.6792 83.1887C26.0157 80.7453 24.684 77.9717 24.684 74.8679V41.6509H32.3774V74.8679C32.3774 76.2547 33.489 77.5645 35.7123 78.7972C37.3632 79.7217 39.0692 80.3711 40.8302 80.7453C42.5252 81.1195 44.3082 81.3066 46.1792 81.3066C48.0063 81.3066 49.7673 81.1195 51.4623 80.7453C53.2453 80.3711 54.9623 79.7217 56.6132 78.7972C58.8585 77.5645 59.9811 76.2547 59.9811 74.8679V41.6509H67.6085V74.8679C67.6085 76.2547 68.7311 77.5645 70.9764 78.7972C72.6274 79.7217 74.3443 80.3711 76.1274 80.7453C77.8223 81.1195 79.5833 81.3066 81.4104 81.3066C83.2814 81.3066 85.0755 81.1195 86.7925 80.7453C88.5314 80.3711 90.2264 79.7217 91.8774 78.7972C94.1006 77.5645 95.2123 76.2547 95.2123 74.8679V41.6509H102.906V74.8679ZM189.283 74.8679C189.283 77.9717 187.951 80.7453 185.288 83.1887C183.064 85.1918 180.28 86.6997 176.934 87.7123C174.072 88.5708 171.024 89 167.788 89C160.259 89 154.382 87.0189 150.156 83.0566C145.929 87.0189 140.063 89 132.557 89C129.321 89 126.283 88.5708 123.443 87.7123C120.097 86.6997 117.302 85.1918 115.057 83.1887C112.393 80.7453 111.061 77.9717 111.061 74.8679V41.6509H118.755V74.8679C118.755 76.2547 119.866 77.5645 122.09 78.7972C123.741 79.7217 125.447 80.3711 127.208 80.7453C128.903 81.1195 130.686 81.3066 132.557 81.3066C134.384 81.3066 136.145 81.1195 137.84 80.7453C139.623 80.3711 141.34 79.7217 142.991 78.7972C145.236 77.5645 146.358 76.2547 146.358 74.8679V41.6509H153.986V74.8679C153.986 76.2547 155.108 77.5645 157.354 78.7972C159.005 79.7217 160.722 80.3711 162.505 80.7453C164.2 81.1195 165.961 81.3066 167.788 81.3066C169.659 81.3066 171.453 81.1195 173.17 80.7453C174.909 80.3711 176.604 79.7217 178.255 78.7972C180.478 77.5645 181.59 76.2547 181.59 74.8679V41.6509H189.283V74.8679Z" fill="white"/>
422
+ </g>
423
+ <defs>
424
+ <clipPath id="clip0_3624_12755">
425
+ <rect width="214" height="135" fill="white"/>
426
+ </clipPath>
427
+ </defs>
428
+ </svg>
429
+ <div style="font-size: 16px; font-weight: 500; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">${text}</div>
430
+ </div>
373
431
  `;
374
- closeButton.onclick = () => {
375
- modal.remove();
376
- };
377
- // Close on backdrop click
378
- modal.onclick = (e) => {
379
- if (e.target === modal) {
380
- modal.remove();
381
- }
382
- };
383
- // Close on Escape key
384
- const handleEscape = (e) => {
385
- if (e.key === "Escape") {
386
- modal.remove();
387
- document.removeEventListener("keydown", handleEscape);
388
- }
389
- };
390
- document.addEventListener("keydown", handleEscape);
391
- imageContainer.appendChild(image);
392
- imageContainer.appendChild(closeButton);
393
- modal.appendChild(imageContainer);
394
- document.body.appendChild(modal);
432
+ return overlay;
433
+ }
434
+ /**
435
+ * Shows a loading overlay in a modal container
436
+ */
437
+ function showModalLoading(text = "Processing...") {
438
+ const modal = document.querySelector(`.${CSS_CLASSES.MODAL}`);
439
+ if (!modal)
440
+ return;
441
+ // Remove any existing loading overlays
442
+ removeModalLoading();
443
+ const loadingOverlay = createLoadingOverlay(text);
444
+ modal.appendChild(loadingOverlay);
445
+ }
446
+ /**
447
+ * Removes loading overlay from modal
448
+ */
449
+ function removeModalLoading() {
450
+ const modal = document.querySelector(`.${CSS_CLASSES.MODAL}`);
451
+ if (!modal)
452
+ return;
453
+ const existingOverlay = modal.querySelector(".ww-loading-overlay");
454
+ if (existingOverlay) {
455
+ existingOverlay.remove();
456
+ }
457
+ }
458
+ /**
459
+ * Shows a loading overlay in the product gallery container
460
+ */
461
+ function showProductLoading(container, text = "Generating virtual try-on...") {
462
+ // Remove any existing loading overlays
463
+ removeProductLoading(container);
464
+ // Make container relative if it's not already positioned
465
+ const computedStyle = window.getComputedStyle(container);
466
+ if (computedStyle.position === "static") {
467
+ container.style.position = "relative";
468
+ }
469
+ const loadingOverlay = createLoadingOverlay(text);
470
+ container.appendChild(loadingOverlay);
471
+ }
472
+ /**
473
+ * Removes loading overlay from product container
474
+ */
475
+ function removeProductLoading(container) {
476
+ const existingOverlay = container.querySelector(".ww-loading-overlay");
477
+ if (existingOverlay) {
478
+ existingOverlay.remove();
479
+ }
395
480
  }
396
481
 
397
482
  function showReviewModal(imageBlob, callbacks) {
@@ -417,6 +502,7 @@
417
502
  z-index: ${Z_INDEX.MODAL};
418
503
  padding: 20px;
419
504
  box-sizing: border-box;
505
+ gap: 20px;
420
506
  `;
421
507
  // Create review container
422
508
  const reviewContainer = document.createElement("div");
@@ -424,7 +510,7 @@
424
510
  position: relative;
425
511
  width: 100%;
426
512
  max-width: 500px;
427
- height: 70vh;
513
+ height: 63vh;
428
514
  background-color: #000;
429
515
  border-radius: 12px;
430
516
  overflow: hidden;
@@ -466,14 +552,10 @@
466
552
  // Create button container
467
553
  const buttonContainer = document.createElement("div");
468
554
  buttonContainer.style.cssText = `
469
- position: absolute;
470
- bottom: 20px;
471
- left: 50%;
472
- transform: translateX(-50%);
473
555
  display: flex;
474
556
  gap: 15px;
475
- width: calc(100% - 40px);
476
- max-width: 300px;
557
+ width: 100%;
558
+ max-width: 500px;
477
559
  `;
478
560
  // Create retake button
479
561
  const retakeButton = document.createElement("button");
@@ -481,14 +563,13 @@
481
563
  retakeButton.style.cssText = `
482
564
  flex: 1;
483
565
  padding: 12px 24px;
484
- background-color: rgba(0, 0, 0, 0.7);
485
- color: white;
566
+ background-color: rgba(255, 255, 255, 0.9);
567
+ color: black;
486
568
  border-radius: 8px;
487
569
  border: none;
488
570
  font-size: 16px;
489
- font-weight: 600;
571
+ font-weight: normal;
490
572
  cursor: pointer;
491
- transition: all 0.2s ease;
492
573
  `;
493
574
  // Create use photo button
494
575
  const usePhotoButton = document.createElement("button");
@@ -501,9 +582,8 @@
501
582
  border-radius: 8px;
502
583
  border: none;
503
584
  font-size: 16px;
504
- font-weight: 600;
585
+ font-weight: normal;
505
586
  cursor: pointer;
506
- transition: all 0.2s ease;
507
587
  `;
508
588
  // Add event listeners
509
589
  closeButton.onclick = () => {
@@ -525,14 +605,17 @@
525
605
  buttonContainer.appendChild(usePhotoButton);
526
606
  reviewContainer.appendChild(image);
527
607
  reviewContainer.appendChild(closeButton);
528
- reviewContainer.appendChild(buttonContainer);
529
608
  modal.appendChild(reviewContainer);
609
+ modal.appendChild(buttonContainer);
530
610
  document.body.appendChild(modal);
531
611
  console.log("[WeWear VTO] Review modal added to DOM");
532
612
  }
533
613
 
534
614
  class VirtualTryOnWidget {
535
615
  constructor(config = {}) {
616
+ this.virtualTryOnImageUrl = null;
617
+ this.isShowingVirtualTryOn = false;
618
+ this.lastApiParams = null;
536
619
  this.config = {
537
620
  baseUrl: config.baseUrl || DEFAULT_CONFIG.BASE_URL,
538
621
  productPageSelector: config.productPageSelector || DEFAULT_CONFIG.PRODUCT_PAGE_SELECTOR,
@@ -655,10 +738,22 @@
655
738
  async processAcceptedImage(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
656
739
  try {
657
740
  console.log("[WeWear VTO] Processing accepted image...");
741
+ // Show loading in the review modal first
742
+ showModalLoading("Processing image...");
743
+ // Store the API parameters for potential refresh
744
+ this.lastApiParams = {
745
+ imageBlob,
746
+ ww_access_token,
747
+ ww_user_id,
748
+ ww_product_id,
749
+ };
658
750
  // Call the API with the accepted image
659
751
  const result = await callVirtualTryOnApi(this.config.baseUrl, ww_access_token, ww_user_id, ww_product_id, imageBlob);
752
+ // Remove modal loading and close modal
753
+ removeModalLoading();
754
+ removeElements(`.${CSS_CLASSES.MODAL}`);
660
755
  if (result === null || result === void 0 ? void 0 : result.imageUrl) {
661
- showImageModal(result.imageUrl);
756
+ this.replaceProductImage(result.imageUrl);
662
757
  }
663
758
  else {
664
759
  console.error("[WeWear VTO] Invalid API response:", result);
@@ -666,7 +761,187 @@
666
761
  }
667
762
  catch (error) {
668
763
  console.error("[WeWear VTO] Error processing accepted image:", error);
764
+ // Remove loading on error
765
+ removeModalLoading();
766
+ }
767
+ }
768
+ /**
769
+ * Recalls the virtual try-on API with the same parameters
770
+ * @private
771
+ */
772
+ async refreshVirtualTryOn() {
773
+ if (!this.lastApiParams) {
774
+ console.warn("[WeWear VTO] No previous API parameters available for refresh");
775
+ return;
776
+ }
777
+ try {
778
+ console.log("[WeWear VTO] Refreshing virtual try-on with previous parameters...");
779
+ // Show loading in the product area
780
+ const container = document.querySelector(this.config.gallerySelector);
781
+ if (container instanceof HTMLElement) {
782
+ showProductLoading(container, "Generating new virtual try-on...");
783
+ }
784
+ // Call the API with the stored parameters
785
+ const result = await callVirtualTryOnApi(this.config.baseUrl, this.lastApiParams.ww_access_token, this.lastApiParams.ww_user_id, this.lastApiParams.ww_product_id, this.lastApiParams.imageBlob);
786
+ if (result === null || result === void 0 ? void 0 : result.imageUrl) {
787
+ this.virtualTryOnImageUrl = result.imageUrl;
788
+ // Find the gallery container and update the image
789
+ const container = document.querySelector(this.config.gallerySelector);
790
+ if (container instanceof HTMLElement) {
791
+ removeProductLoading(container);
792
+ this.showVirtualTryOnImage(container);
793
+ this.updateButtonContainer(container);
794
+ }
795
+ }
796
+ else {
797
+ console.error("[WeWear VTO] Invalid API response on refresh:", result);
798
+ // Remove loading on error
799
+ const container = document.querySelector(this.config.gallerySelector);
800
+ if (container instanceof HTMLElement) {
801
+ removeProductLoading(container);
802
+ }
803
+ }
804
+ }
805
+ catch (error) {
806
+ console.error("[WeWear VTO] Error refreshing virtual try-on:", error);
807
+ // Remove loading on error
808
+ const container = document.querySelector(this.config.gallerySelector);
809
+ if (container instanceof HTMLElement) {
810
+ removeProductLoading(container);
811
+ }
812
+ }
813
+ }
814
+ /**
815
+ * Replaces the product image in the gallery with the virtual try-on result
816
+ * @private
817
+ */
818
+ replaceProductImage(imageUrl) {
819
+ try {
820
+ // Store the virtual try-on image URL
821
+ this.virtualTryOnImageUrl = imageUrl;
822
+ // Find the gallery container
823
+ const container = document.querySelector(this.config.gallerySelector);
824
+ if (!container || !(container instanceof HTMLElement)) {
825
+ console.warn("[WeWear VTO] Gallery container not found for image replacement:", this.config.gallerySelector);
826
+ return;
827
+ }
828
+ // Store the original content if not already stored
829
+ if (!container.hasAttribute("data-ww-original-content")) {
830
+ container.setAttribute("data-ww-original-content", container.innerHTML);
831
+ }
832
+ // Capture original image dimensions before replacement to prevent layout shift
833
+ const originalImg = container.querySelector('img');
834
+ if (originalImg && !container.hasAttribute("data-ww-original-dimensions")) {
835
+ const computedStyle = window.getComputedStyle(originalImg);
836
+ const rect = originalImg.getBoundingClientRect();
837
+ container.setAttribute("data-ww-original-width", computedStyle.width);
838
+ container.setAttribute("data-ww-original-height", computedStyle.height);
839
+ container.setAttribute("data-ww-original-rect-width", rect.width.toString());
840
+ container.setAttribute("data-ww-original-rect-height", rect.height.toString());
841
+ container.setAttribute("data-ww-original-dimensions", "true");
842
+ }
843
+ // Show the virtual try-on image initially
844
+ this.showVirtualTryOnImage(container);
845
+ // Replace the button container with the new multi-button version
846
+ this.updateButtonContainer(container);
847
+ console.log("[WeWear VTO] Product image replaced with virtual try-on result");
848
+ }
849
+ catch (error) {
850
+ console.error("[WeWear VTO] Error replacing product image:", error);
851
+ }
852
+ }
853
+ /**
854
+ * Updates the button container to show all available buttons
855
+ * @private
856
+ */
857
+ updateButtonContainer(container) {
858
+ // Remove existing button containers
859
+ const existingButtons = container.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
860
+ existingButtons.forEach((btn) => {
861
+ btn.remove();
862
+ });
863
+ // Create new button container with all buttons
864
+ const buttonContainer = createButtonContainer(this.config.buttonPosition, this.virtualTryOnImageUrl !== null, async () => {
865
+ await this.handleTryOnClick();
866
+ }, async () => {
867
+ await this.refreshVirtualTryOn();
868
+ }, () => {
869
+ if (this.isShowingVirtualTryOn) {
870
+ this.showOriginalImage(container);
871
+ }
872
+ else {
873
+ this.showVirtualTryOnImage(container);
874
+ }
875
+ this.updateButtonContainer(container);
876
+ }, this.isShowingVirtualTryOn);
877
+ container.appendChild(buttonContainer);
878
+ }
879
+ /**
880
+ * Shows the virtual try-on image in the container
881
+ * @private
882
+ */
883
+ showVirtualTryOnImage(container) {
884
+ if (!this.virtualTryOnImageUrl) {
885
+ console.warn("[WeWear VTO] No virtual try-on image URL available");
886
+ return;
887
+ }
888
+ // Clear existing content except buttons
889
+ const existingButtons = container.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
890
+ container.innerHTML = "";
891
+ // Get stored original dimensions to prevent layout shift
892
+ const originalWidth = container.getAttribute("data-ww-original-width") || '';
893
+ const originalHeight = container.getAttribute("data-ww-original-height") || '';
894
+ const originalRectWidth = container.getAttribute("data-ww-original-rect-width") || '';
895
+ const originalRectHeight = container.getAttribute("data-ww-original-rect-height") || '';
896
+ const image = document.createElement("img");
897
+ image.src = this.virtualTryOnImageUrl;
898
+ image.alt = "Virtual Try-On Result";
899
+ // Use original dimensions to prevent layout shift
900
+ let widthStyle = '100%';
901
+ let heightStyle = '100%';
902
+ // Prefer computed style dimensions, fallback to bounding rect, then container fill
903
+ if (originalWidth && originalWidth !== 'auto' && originalWidth !== '0px') {
904
+ widthStyle = originalWidth;
905
+ }
906
+ else if (originalRectWidth && originalRectWidth !== '0') {
907
+ widthStyle = `${originalRectWidth}px`;
908
+ }
909
+ if (originalHeight && originalHeight !== 'auto' && originalHeight !== '0px') {
910
+ heightStyle = originalHeight;
911
+ }
912
+ else if (originalRectHeight && originalRectHeight !== '0') {
913
+ heightStyle = `${originalRectHeight}px`;
914
+ }
915
+ image.style.cssText = `
916
+ width: ${widthStyle};
917
+ height: ${heightStyle};
918
+ object-fit: contain;
919
+ border-radius: 8px;
920
+ `;
921
+ container.appendChild(image);
922
+ // Re-add buttons
923
+ existingButtons.forEach((btn) => {
924
+ container.appendChild(btn);
925
+ });
926
+ this.isShowingVirtualTryOn = true;
927
+ }
928
+ /**
929
+ * Shows the original product image in the container
930
+ * @private
931
+ */
932
+ showOriginalImage(container) {
933
+ const originalContent = container.getAttribute("data-ww-original-content");
934
+ if (originalContent) {
935
+ // Store existing buttons
936
+ const existingButtons = container.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
937
+ // Restore original content
938
+ container.innerHTML = originalContent;
939
+ // Re-add buttons
940
+ existingButtons.forEach((btn) => {
941
+ container.appendChild(btn);
942
+ });
669
943
  }
944
+ this.isShowingVirtualTryOn = false;
670
945
  }
671
946
  /**
672
947
  * Destroys the widget and cleans up resources
@@ -677,6 +952,10 @@
677
952
  removeElements(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
678
953
  // Remove all modals
679
954
  removeElements(`.${CSS_CLASSES.MODAL}`);
955
+ // Reset state
956
+ this.virtualTryOnImageUrl = null;
957
+ this.isShowingVirtualTryOn = false;
958
+ this.lastApiParams = null;
680
959
  console.log("[WeWear VTO] Widget destroyed successfully");
681
960
  }
682
961
  catch (error) {