@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.
@@ -1 +1,2 @@
1
+ export declare function createButtonContainer(buttonPosition: string, hasVirtualTryOn: boolean | undefined, onCameraClick: () => void, onRefreshClick?: () => void, onToggleClick?: () => void, isShowingVirtualTryOn?: boolean): HTMLElement;
1
2
  export declare function createButton(buttonPosition: string, onClick: () => void): HTMLElement;
@@ -1,4 +1,4 @@
1
- export { createButton } from "./button.js";
1
+ export { createButton, createButtonContainer } from "./button.js";
2
2
  export { type CameraModalCallbacks, showCameraModal } from "./camera-modal.js";
3
- export { showImageModal } from "./image-modal.js";
3
+ export { createLoadingOverlay, removeModalLoading, removeProductLoading, showModalLoading, showProductLoading, } from "./loading-overlay.js";
4
4
  export { type ReviewModalCallbacks, showReviewModal } from "./review-modal.js";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Creates a loading overlay that can be shown in different containers
3
+ */
4
+ export declare function createLoadingOverlay(text?: string): HTMLElement;
5
+ /**
6
+ * Shows a loading overlay in a modal container
7
+ */
8
+ export declare function showModalLoading(text?: string): void;
9
+ /**
10
+ * Removes loading overlay from modal
11
+ */
12
+ export declare function removeModalLoading(): void;
13
+ /**
14
+ * Shows a loading overlay in the product gallery container
15
+ */
16
+ export declare function showProductLoading(container: HTMLElement, text?: string): void;
17
+ /**
18
+ * Removes loading overlay from product container
19
+ */
20
+ export declare function removeProductLoading(container: HTMLElement): void;
package/dist/index.d.ts CHANGED
@@ -14,5 +14,5 @@ export interface VirtualTryOnAPIRequest {
14
14
  ww_product_id: string;
15
15
  ww_image: Blob;
16
16
  }
17
- export { destroyVirtualTryOn, getWidgetInstance, initVirtualTryOn } from "./installer.js";
17
+ export { destroyVirtualTryOn, getWidgetInstance, initVirtualTryOn, } from "./installer.js";
18
18
  export { VirtualTryOnWidget } from "./widget.js";
package/dist/index.esm.js CHANGED
@@ -139,44 +139,117 @@ function removeElements(selector) {
139
139
  });
140
140
  }
141
141
 
142
- function createButton(buttonPosition, onClick) {
142
+ function createButtonContainer(buttonPosition, hasVirtualTryOn = false, onCameraClick, onRefreshClick, onToggleClick, isShowingVirtualTryOn = false) {
143
143
  const container = document.createElement("div");
144
- container.className = CSS_CLASSES.BUTTON_CONTAINER;
144
+ container.className = `${CSS_CLASSES.BUTTON_CONTAINER} ww-button-group`;
145
145
  const positionStyles = getPositionStyles(buttonPosition);
146
146
  container.style.cssText = `
147
147
  position: absolute;
148
148
  ${positionStyles}
149
149
  display: flex;
150
- border-radius: 50%;
151
- background: #000000;
150
+ border-radius: 9999px;
151
+ background: white;
152
152
  padding: 4px;
153
153
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
154
154
  z-index: ${Z_INDEX.BUTTON};
155
155
  `;
156
- const button = document.createElement("button");
157
- button.type = "button";
158
- button.className = CSS_CLASSES.BUTTON;
159
- button.setAttribute("aria-label", "Virtual Try-On");
160
- button.style.cssText = `
156
+ // Camera button
157
+ const cameraButton = document.createElement("button");
158
+ cameraButton.type = "button";
159
+ cameraButton.className = `${CSS_CLASSES.BUTTON} ww-camera-btn`;
160
+ cameraButton.setAttribute("aria-label", "Virtual Try-On");
161
+ cameraButton.style.cssText = `
161
162
  display: flex;
162
163
  width: 36px;
163
164
  height: 36px;
164
165
  cursor: pointer;
165
166
  align-items: center;
166
167
  justify-content: center;
167
- border-radius: 50%;
168
- padding: 10px;
168
+ border-radius: 9999px;
169
+ padding: 8px;
169
170
  border: none;
170
171
  background: transparent;
171
- color: #ffffff;
172
+ color: currentColor;
172
173
  `;
173
- button.innerHTML = `
174
- <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>
174
+ cameraButton.innerHTML = `
175
+ <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">
176
+ <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>
177
+ <circle cx="12" cy="13" r="3"></circle>
178
+ </svg>
175
179
  `;
176
- button.onclick = onClick;
177
- container.appendChild(button);
180
+ cameraButton.onclick = onCameraClick;
181
+ container.appendChild(cameraButton);
182
+ // Add refresh and toggle buttons if we have a virtual try-on
183
+ if (hasVirtualTryOn) {
184
+ // Refresh button - calls API again
185
+ if (onRefreshClick) {
186
+ const refreshButton = document.createElement("button");
187
+ refreshButton.type = "button";
188
+ refreshButton.className = "ww-refresh-btn";
189
+ refreshButton.setAttribute("aria-label", "Refresh virtual try-on");
190
+ refreshButton.style.cssText = `
191
+ display: flex;
192
+ width: 36px;
193
+ height: 36px;
194
+ cursor: pointer;
195
+ align-items: center;
196
+ justify-content: center;
197
+ border-radius: 9999px;
198
+ padding: 8px;
199
+ border: none;
200
+ background: transparent;
201
+ color: currentColor;
202
+ `;
203
+ refreshButton.innerHTML = `
204
+ <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">
205
+ <path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
206
+ <path d="M3 3v5h5"></path>
207
+ <path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"></path>
208
+ <path d="M16 16h5v5"></path>
209
+ </svg>
210
+ `;
211
+ refreshButton.onclick = onRefreshClick;
212
+ container.appendChild(refreshButton);
213
+ }
214
+ // Toggle button (scan-face) - switches between original and virtual try-on
215
+ if (onToggleClick) {
216
+ const toggleButton = document.createElement("button");
217
+ toggleButton.type = "button";
218
+ toggleButton.className = "ww-toggle-btn";
219
+ toggleButton.setAttribute("aria-label", isShowingVirtualTryOn ? "Show Original Image" : "Show Virtual Try-On");
220
+ toggleButton.style.cssText = `
221
+ display: flex;
222
+ width: 36px;
223
+ height: 36px;
224
+ cursor: pointer;
225
+ align-items: center;
226
+ justify-content: center;
227
+ border-radius: 9999px;
228
+ padding: 8px;
229
+ border: none;
230
+ background: ${isShowingVirtualTryOn ? "rgba(0, 0, 0)" : "none"};
231
+ color: ${isShowingVirtualTryOn ? "white" : "currentColor"};
232
+ `;
233
+ toggleButton.innerHTML = `
234
+ <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">
235
+ <path d="M3 7V5a2 2 0 0 1 2-2h2"></path>
236
+ <path d="M17 3h2a2 2 0 0 1 2 2v2"></path>
237
+ <path d="M21 17v2a2 2 0 0 1-2 2h-2"></path>
238
+ <path d="M7 21H5a2 2 0 0 1-2-2v-2"></path>
239
+ <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
240
+ <path d="M9 9h.01"></path>
241
+ <path d="M15 9h.01"></path>
242
+ </svg>
243
+ `;
244
+ toggleButton.onclick = onToggleClick;
245
+ container.appendChild(toggleButton);
246
+ }
247
+ }
178
248
  return container;
179
249
  }
250
+ function createButton(buttonPosition, onClick) {
251
+ return createButtonContainer(buttonPosition, false, onClick);
252
+ }
180
253
 
181
254
  function showCameraModal(callbacks) {
182
255
  console.log("[WeWear VTO] Opening camera modal...");
@@ -300,92 +373,104 @@ function showCameraModal(callbacks) {
300
373
  startCamera(video, loadingIndicator, captureButton);
301
374
  }
302
375
 
303
- function showImageModal(imageUrl) {
304
- // Remove any existing modals first
305
- removeElements(`.${CSS_CLASSES.MODAL}`);
306
- // Create modal container
307
- const modal = document.createElement("div");
308
- modal.className = CSS_CLASSES.MODAL;
309
- modal.style.cssText = `
310
- position: fixed;
376
+ /**
377
+ * Creates a loading overlay that can be shown in different containers
378
+ */
379
+ function createLoadingOverlay(text = "Processing...") {
380
+ const overlay = document.createElement("div");
381
+ overlay.className = "ww-loading-overlay";
382
+ overlay.style.cssText = `
383
+ position: absolute;
311
384
  top: 0;
312
385
  left: 0;
313
386
  width: 100%;
314
387
  height: 100%;
315
- background-color: rgba(0, 0, 0, 0.9);
316
- display: flex;
317
- align-items: center;
318
- justify-content: center;
319
- z-index: ${Z_INDEX.MODAL};
320
- padding: 20px;
321
- box-sizing: border-box;
322
- `;
323
- // Create image container
324
- const imageContainer = document.createElement("div");
325
- imageContainer.style.cssText = `
326
- position: relative;
327
- width: 100%;
328
- max-width: 500px;
329
- height: 70vh;
330
- background-color: #000;
331
- border-radius: 12px;
332
- overflow: hidden;
388
+ background-color: rgba(0, 0, 0, 0.8);
333
389
  display: flex;
334
390
  flex-direction: column;
335
391
  align-items: center;
336
392
  justify-content: center;
393
+ z-index: ${Z_INDEX.MODAL + 1};
394
+ border-radius: inherit;
337
395
  `;
338
- // Create image element
339
- const image = document.createElement("img");
340
- image.src = imageUrl;
341
- image.alt = "Virtual Try-On Result";
342
- image.style.cssText = `
343
- width: 100%;
344
- height: 100%;
345
- object-fit: cover;
346
- border-radius: 12px;
347
- `;
348
- // Create close button
349
- const closeButton = document.createElement("button");
350
- closeButton.innerHTML = "×";
351
- closeButton.style.cssText = `
352
- position: absolute;
353
- top: 15px;
354
- right: 15px;
355
- width: 40px;
356
- height: 40px;
357
- border: none;
358
- background-color: rgba(0, 0, 0, 0.7);
359
- color: white;
360
- font-size: 24px;
361
- font-weight: bold;
362
- cursor: pointer;
363
- border-radius: 50%;
364
- display: flex;
365
- align-items: center;
366
- justify-content: center;
367
- `;
368
- closeButton.onclick = () => {
369
- modal.remove();
370
- };
371
- // Close on backdrop click
372
- modal.onclick = (e) => {
373
- if (e.target === modal) {
374
- modal.remove();
396
+ // Add CSS animation to the document if not already added
397
+ if (!document.getElementById("ww-loading-animation")) {
398
+ const style = document.createElement("style");
399
+ style.id = "ww-loading-animation";
400
+ style.textContent = `
401
+ @keyframes ww-pulse {
402
+ 0%, 100% { opacity: 0.7; transform: scale(1); }
403
+ 50% { opacity: 1; transform: scale(1.05); }
375
404
  }
376
- };
377
- // Close on Escape key
378
- const handleEscape = (e) => {
379
- if (e.key === "Escape") {
380
- modal.remove();
381
- document.removeEventListener("keydown", handleEscape);
405
+ .ww-loading-overlay .ww-logo {
406
+ animation: ww-pulse 2s ease-in-out infinite;
382
407
  }
383
- };
384
- document.addEventListener("keydown", handleEscape);
385
- imageContainer.appendChild(image);
386
- imageContainer.appendChild(closeButton);
387
- modal.appendChild(imageContainer);
388
- document.body.appendChild(modal);
408
+ `;
409
+ document.head.appendChild(style);
410
+ }
411
+ overlay.innerHTML = `
412
+ <div style="display: flex; flex-direction: column; align-items: center; gap: 24px; color: white;">
413
+ <svg class="ww-logo" width="80" height="50" viewBox="0 0 214 135" fill="none" xmlns="http://www.w3.org/2000/svg">
414
+ <g clip-path="url(#clip0_3624_12755)">
415
+ <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"/>
416
+ </g>
417
+ <defs>
418
+ <clipPath id="clip0_3624_12755">
419
+ <rect width="214" height="135" fill="white"/>
420
+ </clipPath>
421
+ </defs>
422
+ </svg>
423
+ <div style="font-size: 16px; font-weight: 500; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">${text}</div>
424
+ </div>
425
+ `;
426
+ return overlay;
427
+ }
428
+ /**
429
+ * Shows a loading overlay in a modal container
430
+ */
431
+ function showModalLoading(text = "Processing...") {
432
+ const modal = document.querySelector(`.${CSS_CLASSES.MODAL}`);
433
+ if (!modal)
434
+ return;
435
+ // Remove any existing loading overlays
436
+ removeModalLoading();
437
+ const loadingOverlay = createLoadingOverlay(text);
438
+ modal.appendChild(loadingOverlay);
439
+ }
440
+ /**
441
+ * Removes loading overlay from modal
442
+ */
443
+ function removeModalLoading() {
444
+ const modal = document.querySelector(`.${CSS_CLASSES.MODAL}`);
445
+ if (!modal)
446
+ return;
447
+ const existingOverlay = modal.querySelector(".ww-loading-overlay");
448
+ if (existingOverlay) {
449
+ existingOverlay.remove();
450
+ }
451
+ }
452
+ /**
453
+ * Shows a loading overlay in the product gallery container
454
+ */
455
+ function showProductLoading(container, text = "Generating virtual try-on...") {
456
+ // Remove any existing loading overlays
457
+ removeProductLoading(container);
458
+ // Make container relative if it's not already positioned
459
+ const computedStyle = window.getComputedStyle(container);
460
+ if (computedStyle.position === "static") {
461
+ container.style.position = "relative";
462
+ }
463
+ const loadingOverlay = createLoadingOverlay(text);
464
+ container.appendChild(loadingOverlay);
465
+ }
466
+ /**
467
+ * Removes loading overlay from product container
468
+ */
469
+ function removeProductLoading(container) {
470
+ const existingOverlay = container.querySelector(".ww-loading-overlay");
471
+ if (existingOverlay) {
472
+ existingOverlay.remove();
473
+ }
389
474
  }
390
475
 
391
476
  function showReviewModal(imageBlob, callbacks) {
@@ -411,6 +496,7 @@ function showReviewModal(imageBlob, callbacks) {
411
496
  z-index: ${Z_INDEX.MODAL};
412
497
  padding: 20px;
413
498
  box-sizing: border-box;
499
+ gap: 20px;
414
500
  `;
415
501
  // Create review container
416
502
  const reviewContainer = document.createElement("div");
@@ -418,7 +504,7 @@ function showReviewModal(imageBlob, callbacks) {
418
504
  position: relative;
419
505
  width: 100%;
420
506
  max-width: 500px;
421
- height: 70vh;
507
+ height: 63vh;
422
508
  background-color: #000;
423
509
  border-radius: 12px;
424
510
  overflow: hidden;
@@ -460,14 +546,10 @@ function showReviewModal(imageBlob, callbacks) {
460
546
  // Create button container
461
547
  const buttonContainer = document.createElement("div");
462
548
  buttonContainer.style.cssText = `
463
- position: absolute;
464
- bottom: 20px;
465
- left: 50%;
466
- transform: translateX(-50%);
467
549
  display: flex;
468
550
  gap: 15px;
469
- width: calc(100% - 40px);
470
- max-width: 300px;
551
+ width: 100%;
552
+ max-width: 500px;
471
553
  `;
472
554
  // Create retake button
473
555
  const retakeButton = document.createElement("button");
@@ -475,14 +557,13 @@ function showReviewModal(imageBlob, callbacks) {
475
557
  retakeButton.style.cssText = `
476
558
  flex: 1;
477
559
  padding: 12px 24px;
478
- background-color: rgba(0, 0, 0, 0.7);
479
- color: white;
560
+ background-color: rgba(255, 255, 255, 0.9);
561
+ color: black;
480
562
  border-radius: 8px;
481
563
  border: none;
482
564
  font-size: 16px;
483
- font-weight: 600;
565
+ font-weight: normal;
484
566
  cursor: pointer;
485
- transition: all 0.2s ease;
486
567
  `;
487
568
  // Create use photo button
488
569
  const usePhotoButton = document.createElement("button");
@@ -495,9 +576,8 @@ function showReviewModal(imageBlob, callbacks) {
495
576
  border-radius: 8px;
496
577
  border: none;
497
578
  font-size: 16px;
498
- font-weight: 600;
579
+ font-weight: normal;
499
580
  cursor: pointer;
500
- transition: all 0.2s ease;
501
581
  `;
502
582
  // Add event listeners
503
583
  closeButton.onclick = () => {
@@ -519,14 +599,17 @@ function showReviewModal(imageBlob, callbacks) {
519
599
  buttonContainer.appendChild(usePhotoButton);
520
600
  reviewContainer.appendChild(image);
521
601
  reviewContainer.appendChild(closeButton);
522
- reviewContainer.appendChild(buttonContainer);
523
602
  modal.appendChild(reviewContainer);
603
+ modal.appendChild(buttonContainer);
524
604
  document.body.appendChild(modal);
525
605
  console.log("[WeWear VTO] Review modal added to DOM");
526
606
  }
527
607
 
528
608
  class VirtualTryOnWidget {
529
609
  constructor(config = {}) {
610
+ this.virtualTryOnImageUrl = null;
611
+ this.isShowingVirtualTryOn = false;
612
+ this.lastApiParams = null;
530
613
  this.config = {
531
614
  baseUrl: config.baseUrl || DEFAULT_CONFIG.BASE_URL,
532
615
  productPageSelector: config.productPageSelector || DEFAULT_CONFIG.PRODUCT_PAGE_SELECTOR,
@@ -649,10 +732,22 @@ class VirtualTryOnWidget {
649
732
  async processAcceptedImage(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
650
733
  try {
651
734
  console.log("[WeWear VTO] Processing accepted image...");
735
+ // Show loading in the review modal first
736
+ showModalLoading("Processing image...");
737
+ // Store the API parameters for potential refresh
738
+ this.lastApiParams = {
739
+ imageBlob,
740
+ ww_access_token,
741
+ ww_user_id,
742
+ ww_product_id,
743
+ };
652
744
  // Call the API with the accepted image
653
745
  const result = await callVirtualTryOnApi(this.config.baseUrl, ww_access_token, ww_user_id, ww_product_id, imageBlob);
746
+ // Remove modal loading and close modal
747
+ removeModalLoading();
748
+ removeElements(`.${CSS_CLASSES.MODAL}`);
654
749
  if (result === null || result === void 0 ? void 0 : result.imageUrl) {
655
- showImageModal(result.imageUrl);
750
+ this.replaceProductImage(result.imageUrl);
656
751
  }
657
752
  else {
658
753
  console.error("[WeWear VTO] Invalid API response:", result);
@@ -660,7 +755,187 @@ class VirtualTryOnWidget {
660
755
  }
661
756
  catch (error) {
662
757
  console.error("[WeWear VTO] Error processing accepted image:", error);
758
+ // Remove loading on error
759
+ removeModalLoading();
760
+ }
761
+ }
762
+ /**
763
+ * Recalls the virtual try-on API with the same parameters
764
+ * @private
765
+ */
766
+ async refreshVirtualTryOn() {
767
+ if (!this.lastApiParams) {
768
+ console.warn("[WeWear VTO] No previous API parameters available for refresh");
769
+ return;
770
+ }
771
+ try {
772
+ console.log("[WeWear VTO] Refreshing virtual try-on with previous parameters...");
773
+ // Show loading in the product area
774
+ const container = document.querySelector(this.config.gallerySelector);
775
+ if (container instanceof HTMLElement) {
776
+ showProductLoading(container, "Generating new virtual try-on...");
777
+ }
778
+ // Call the API with the stored parameters
779
+ 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);
780
+ if (result === null || result === void 0 ? void 0 : result.imageUrl) {
781
+ this.virtualTryOnImageUrl = result.imageUrl;
782
+ // Find the gallery container and update the image
783
+ const container = document.querySelector(this.config.gallerySelector);
784
+ if (container instanceof HTMLElement) {
785
+ removeProductLoading(container);
786
+ this.showVirtualTryOnImage(container);
787
+ this.updateButtonContainer(container);
788
+ }
789
+ }
790
+ else {
791
+ console.error("[WeWear VTO] Invalid API response on refresh:", result);
792
+ // Remove loading on error
793
+ const container = document.querySelector(this.config.gallerySelector);
794
+ if (container instanceof HTMLElement) {
795
+ removeProductLoading(container);
796
+ }
797
+ }
798
+ }
799
+ catch (error) {
800
+ console.error("[WeWear VTO] Error refreshing virtual try-on:", error);
801
+ // Remove loading on error
802
+ const container = document.querySelector(this.config.gallerySelector);
803
+ if (container instanceof HTMLElement) {
804
+ removeProductLoading(container);
805
+ }
806
+ }
807
+ }
808
+ /**
809
+ * Replaces the product image in the gallery with the virtual try-on result
810
+ * @private
811
+ */
812
+ replaceProductImage(imageUrl) {
813
+ try {
814
+ // Store the virtual try-on image URL
815
+ this.virtualTryOnImageUrl = imageUrl;
816
+ // Find the gallery container
817
+ const container = document.querySelector(this.config.gallerySelector);
818
+ if (!container || !(container instanceof HTMLElement)) {
819
+ console.warn("[WeWear VTO] Gallery container not found for image replacement:", this.config.gallerySelector);
820
+ return;
821
+ }
822
+ // Store the original content if not already stored
823
+ if (!container.hasAttribute("data-ww-original-content")) {
824
+ container.setAttribute("data-ww-original-content", container.innerHTML);
825
+ }
826
+ // Capture original image dimensions before replacement to prevent layout shift
827
+ const originalImg = container.querySelector('img');
828
+ if (originalImg && !container.hasAttribute("data-ww-original-dimensions")) {
829
+ const computedStyle = window.getComputedStyle(originalImg);
830
+ const rect = originalImg.getBoundingClientRect();
831
+ container.setAttribute("data-ww-original-width", computedStyle.width);
832
+ container.setAttribute("data-ww-original-height", computedStyle.height);
833
+ container.setAttribute("data-ww-original-rect-width", rect.width.toString());
834
+ container.setAttribute("data-ww-original-rect-height", rect.height.toString());
835
+ container.setAttribute("data-ww-original-dimensions", "true");
836
+ }
837
+ // Show the virtual try-on image initially
838
+ this.showVirtualTryOnImage(container);
839
+ // Replace the button container with the new multi-button version
840
+ this.updateButtonContainer(container);
841
+ console.log("[WeWear VTO] Product image replaced with virtual try-on result");
842
+ }
843
+ catch (error) {
844
+ console.error("[WeWear VTO] Error replacing product image:", error);
845
+ }
846
+ }
847
+ /**
848
+ * Updates the button container to show all available buttons
849
+ * @private
850
+ */
851
+ updateButtonContainer(container) {
852
+ // Remove existing button containers
853
+ const existingButtons = container.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
854
+ existingButtons.forEach((btn) => {
855
+ btn.remove();
856
+ });
857
+ // Create new button container with all buttons
858
+ const buttonContainer = createButtonContainer(this.config.buttonPosition, this.virtualTryOnImageUrl !== null, async () => {
859
+ await this.handleTryOnClick();
860
+ }, async () => {
861
+ await this.refreshVirtualTryOn();
862
+ }, () => {
863
+ if (this.isShowingVirtualTryOn) {
864
+ this.showOriginalImage(container);
865
+ }
866
+ else {
867
+ this.showVirtualTryOnImage(container);
868
+ }
869
+ this.updateButtonContainer(container);
870
+ }, this.isShowingVirtualTryOn);
871
+ container.appendChild(buttonContainer);
872
+ }
873
+ /**
874
+ * Shows the virtual try-on image in the container
875
+ * @private
876
+ */
877
+ showVirtualTryOnImage(container) {
878
+ if (!this.virtualTryOnImageUrl) {
879
+ console.warn("[WeWear VTO] No virtual try-on image URL available");
880
+ return;
881
+ }
882
+ // Clear existing content except buttons
883
+ const existingButtons = container.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
884
+ container.innerHTML = "";
885
+ // Get stored original dimensions to prevent layout shift
886
+ const originalWidth = container.getAttribute("data-ww-original-width") || '';
887
+ const originalHeight = container.getAttribute("data-ww-original-height") || '';
888
+ const originalRectWidth = container.getAttribute("data-ww-original-rect-width") || '';
889
+ const originalRectHeight = container.getAttribute("data-ww-original-rect-height") || '';
890
+ const image = document.createElement("img");
891
+ image.src = this.virtualTryOnImageUrl;
892
+ image.alt = "Virtual Try-On Result";
893
+ // Use original dimensions to prevent layout shift
894
+ let widthStyle = '100%';
895
+ let heightStyle = '100%';
896
+ // Prefer computed style dimensions, fallback to bounding rect, then container fill
897
+ if (originalWidth && originalWidth !== 'auto' && originalWidth !== '0px') {
898
+ widthStyle = originalWidth;
899
+ }
900
+ else if (originalRectWidth && originalRectWidth !== '0') {
901
+ widthStyle = `${originalRectWidth}px`;
902
+ }
903
+ if (originalHeight && originalHeight !== 'auto' && originalHeight !== '0px') {
904
+ heightStyle = originalHeight;
905
+ }
906
+ else if (originalRectHeight && originalRectHeight !== '0') {
907
+ heightStyle = `${originalRectHeight}px`;
908
+ }
909
+ image.style.cssText = `
910
+ width: ${widthStyle};
911
+ height: ${heightStyle};
912
+ object-fit: contain;
913
+ border-radius: 8px;
914
+ `;
915
+ container.appendChild(image);
916
+ // Re-add buttons
917
+ existingButtons.forEach((btn) => {
918
+ container.appendChild(btn);
919
+ });
920
+ this.isShowingVirtualTryOn = true;
921
+ }
922
+ /**
923
+ * Shows the original product image in the container
924
+ * @private
925
+ */
926
+ showOriginalImage(container) {
927
+ const originalContent = container.getAttribute("data-ww-original-content");
928
+ if (originalContent) {
929
+ // Store existing buttons
930
+ const existingButtons = container.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
931
+ // Restore original content
932
+ container.innerHTML = originalContent;
933
+ // Re-add buttons
934
+ existingButtons.forEach((btn) => {
935
+ container.appendChild(btn);
936
+ });
663
937
  }
938
+ this.isShowingVirtualTryOn = false;
664
939
  }
665
940
  /**
666
941
  * Destroys the widget and cleans up resources
@@ -671,6 +946,10 @@ class VirtualTryOnWidget {
671
946
  removeElements(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
672
947
  // Remove all modals
673
948
  removeElements(`.${CSS_CLASSES.MODAL}`);
949
+ // Reset state
950
+ this.virtualTryOnImageUrl = null;
951
+ this.isShowingVirtualTryOn = false;
952
+ this.lastApiParams = null;
674
953
  console.log("[WeWear VTO] Widget destroyed successfully");
675
954
  }
676
955
  catch (error) {