@wewear/virtual-try-on 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,12 +4,31 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.WeWearVirtualTryOn = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- /**
8
- * WeWear Virtual Try-On Widget Implementation
9
- *
10
- * Core widget class that handles virtual try-on functionality integration.
11
- * Manages button creation, API communication, and modal display.
12
- */
7
+ async function callVirtualTryOnApi(baseUrl, ww_access_token, ww_user_id, ww_product_id, ww_image) {
8
+ try {
9
+ const formData = new FormData();
10
+ formData.append("ww_user_id", ww_user_id);
11
+ formData.append("ww_product_id", ww_product_id);
12
+ formData.append("ww_image", ww_image, "captured-image.jpg");
13
+ formData.append("ww_access_token", ww_access_token);
14
+ const response = await fetch(`${baseUrl}/api/virtual-try-on`, {
15
+ method: "POST",
16
+ body: formData,
17
+ });
18
+ if (!response.ok) {
19
+ console.error("[WeWear VTO] API request failed:", response.status, response.statusText);
20
+ return null;
21
+ }
22
+ const result = await response.json();
23
+ console.log("[WeWear VTO] API response:", result);
24
+ return result;
25
+ }
26
+ catch (error) {
27
+ console.error("[WeWear VTO] API call failed:", error);
28
+ return null;
29
+ }
30
+ }
31
+
13
32
  /** Default configuration constants */
14
33
  const DEFAULT_CONFIG = {
15
34
  BASE_URL: "https://virtual-try-on-widget.vercel.app",
@@ -29,335 +48,500 @@
29
48
  BUTTON: 10,
30
49
  MODAL: 99999,
31
50
  };
32
- class VirtualTryOnWidget {
33
- constructor(config = {}) {
34
- this.config = {
35
- baseUrl: config.baseUrl || DEFAULT_CONFIG.BASE_URL,
36
- productPageSelector: config.productPageSelector || DEFAULT_CONFIG.PRODUCT_PAGE_SELECTOR,
37
- gallerySelector: config.gallerySelector || DEFAULT_CONFIG.GALLERY_SELECTOR,
38
- skuSelector: config.skuSelector || DEFAULT_CONFIG.SKU_SELECTOR,
39
- buttonPosition: config.buttonPosition || DEFAULT_CONFIG.BUTTON_POSITION,
51
+ /** Camera constraints for optimal capture */
52
+ const CAMERA_CONSTRAINTS = {
53
+ video: {
54
+ width: { ideal: 1280 },
55
+ height: { ideal: 720 },
56
+ facingMode: "user", // Front-facing camera
57
+ },
58
+ audio: false,
59
+ };
60
+ /** Image capture settings */
61
+ const IMAGE_SETTINGS = {
62
+ FORMAT: "image/jpeg",
63
+ QUALITY: 0.8,
64
+ };
65
+
66
+ async function startCamera(video, loadingIndicator, captureButton) {
67
+ console.log("[WeWear VTO] Starting camera...");
68
+ try {
69
+ console.log("[WeWear VTO] Requesting camera access...");
70
+ const stream = await navigator.mediaDevices.getUserMedia(CAMERA_CONSTRAINTS);
71
+ video.srcObject = stream;
72
+ video.onloadedmetadata = () => {
73
+ console.log("[WeWear VTO] Camera stream loaded successfully");
74
+ loadingIndicator.style.display = "none";
75
+ captureButton.style.display = "flex";
40
76
  };
41
77
  }
42
- /**
43
- * Retrieves a cookie value by name
44
- * @param name - Cookie name to retrieve
45
- * @returns Cookie value or null if not found
46
- */
47
- getCookie(name) {
48
- var _a;
49
- if (typeof document === "undefined")
50
- return null;
51
- const value = `; ${document.cookie}`;
52
- const parts = value.split(`; ${name}=`);
53
- if (parts.length === 2) {
54
- const part = parts.pop();
55
- return part ? ((_a = part.split(";").shift()) === null || _a === void 0 ? void 0 : _a.trim()) || null : null;
56
- }
57
- return null;
78
+ catch (error) {
79
+ console.error("[WeWear VTO] Camera access error:", error);
80
+ loadingIndicator.innerHTML =
81
+ "Camera access denied. Please allow camera permissions.";
58
82
  }
59
- /**
60
- * Makes API call to virtual try-on service
61
- * @param ww_access_token - Optional authentication token
62
- * @param ww_user_id - User identifier
63
- /**
64
- * Creates the virtual try-on button element
65
- * @param onClick - Click handler function
66
- * @returns Button container element
67
- */
68
- createButton(onClick) {
69
- const container = document.createElement("div");
70
- container.className = CSS_CLASSES.BUTTON_CONTAINER;
71
- const positionStyles = this.getPositionStyles();
72
- container.style.cssText = `
73
- position: absolute;
74
- ${positionStyles}
75
- display: flex;
76
- border-radius: 50%;
77
- background: #000000;
78
- padding: 4px;
79
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
80
- z-index: ${Z_INDEX.BUTTON};
81
- transition: transform 0.2s ease, box-shadow 0.2s ease;
82
- `;
83
- const button = document.createElement("button");
84
- button.type = "button";
85
- button.className = CSS_CLASSES.BUTTON;
86
- button.setAttribute("aria-label", "Virtual Try-On");
87
- button.setAttribute("title", "Try this product virtually");
88
- button.style.cssText = `
89
- display: flex;
90
- width: 36px;
91
- height: 36px;
92
- cursor: pointer;
93
- align-items: center;
94
- justify-content: center;
95
- border-radius: 50%;
96
- padding: 10px;
97
- border: none;
98
- background: transparent;
99
- color: #ffffff;
100
- `;
101
- // Add hover effects
102
- container.addEventListener("mouseenter", () => {
103
- container.style.transform = "scale(1.05)";
104
- container.style.boxShadow = "0 30px 60px -12px rgba(0, 0, 0, 0.35)";
105
- });
106
- container.addEventListener("mouseleave", () => {
107
- container.style.transform = "scale(1)";
108
- container.style.boxShadow = "0 25px 50px -12px rgba(0, 0, 0, 0.25)";
83
+ }
84
+ function stopCamera(video) {
85
+ const stream = video.srcObject;
86
+ if (stream) {
87
+ const tracks = stream.getTracks();
88
+ tracks.forEach((track) => {
89
+ track.stop();
109
90
  });
110
- button.innerHTML = `
111
- <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" aria-hidden="true">
112
- <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>
113
- <circle cx="12" cy="13" r="3"></circle>
114
- </svg>
115
- `;
116
- button.onclick = onClick;
117
- container.appendChild(button);
118
- return container;
91
+ video.srcObject = null;
119
92
  }
120
- /**
121
- * Gets CSS position styles based on button position configuration
122
- * @returns CSS position string
123
- */
124
- getPositionStyles() {
125
- switch (this.config.buttonPosition) {
126
- case "bottom-left":
127
- return "left: 20px; bottom: 20px;";
128
- case "top-right":
129
- return "right: 20px; top: 20px;";
130
- case "top-left":
131
- return "left: 20px; top: 20px;";
132
- default:
133
- return "right: 20px; bottom: 20px;";
134
- }
93
+ }
94
+ async function captureImageFromVideo(video, canvas) {
95
+ // Set canvas dimensions to match video
96
+ canvas.width = video.videoWidth;
97
+ canvas.height = video.videoHeight;
98
+ // Draw video frame to canvas
99
+ const ctx = canvas.getContext("2d");
100
+ if (!ctx) {
101
+ throw new Error("Could not get canvas context");
135
102
  }
136
- /**
137
- * Displays the virtual try-on result in a modal
138
- * @param imageUrl - URL of the virtual try-on image
139
- */
140
- showImageModal(imageUrl) {
141
- // Remove any existing modals first
142
- const existingModals = document.querySelectorAll(`.${CSS_CLASSES.MODAL}`);
143
- existingModals.forEach((modal) => {
144
- modal.remove();
145
- });
146
- // Create modal container
147
- const modal = document.createElement("div");
148
- modal.className = CSS_CLASSES.MODAL;
149
- modal.style.cssText = `
150
- position: fixed;
151
- top: 0;
152
- left: 0;
153
- width: 100%;
154
- height: 100%;
155
- background-color: rgba(0, 0, 0, 0.9);
156
- display: flex;
157
- align-items: center;
158
- justify-content: center;
159
- z-index: ${Z_INDEX.MODAL};
160
- padding: 20px;
161
- box-sizing: border-box;
162
- `;
163
- // Create image container
164
- const imageContainer = document.createElement("div");
165
- imageContainer.style.cssText = `
166
- position: relative;
167
- max-width: 90%;
168
- max-height: 90%;
169
- background-color: white;
170
- border-radius: 8px;
171
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
172
- overflow: hidden;
173
- `;
174
- // Create image element
175
- const image = document.createElement("img");
176
- image.src = imageUrl;
177
- image.alt = "Virtual Try-On Result";
178
- image.style.cssText = `
179
- width: 100%;
180
- height: 100%;
181
- object-fit: contain;
182
- display: block;
183
- `;
184
- // Create close button
185
- const closeButton = document.createElement("button");
186
- closeButton.innerHTML = "×";
187
- closeButton.style.cssText = `
188
- position: absolute;
189
- top: 10px;
190
- right: 10px;
191
- width: 30px;
192
- height: 30px;
193
- border: none;
194
- background-color: rgba(0, 0, 0, 0.7);
195
- color: white;
196
- font-size: 20px;
197
- font-weight: bold;
198
- cursor: pointer;
199
- border-radius: 50%;
200
- display: flex;
201
- align-items: center;
202
- justify-content: center;
203
- `;
204
- closeButton.onclick = () => {
205
- modal.remove();
206
- };
207
- // Close on backdrop click
208
- modal.onclick = (e) => {
209
- if (e.target === modal) {
210
- modal.remove();
103
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
104
+ // Convert canvas to blob
105
+ return new Promise((resolve, reject) => {
106
+ canvas.toBlob((blob) => {
107
+ if (blob) {
108
+ resolve(blob);
211
109
  }
212
- };
213
- // Close on Escape key
214
- const handleEscape = (e) => {
215
- if (e.key === "Escape") {
216
- modal.remove();
217
- document.removeEventListener("keydown", handleEscape);
110
+ else {
111
+ reject(new Error("Failed to create blob from canvas"));
218
112
  }
219
- };
220
- document.addEventListener("keydown", handleEscape);
221
- imageContainer.appendChild(image);
222
- imageContainer.appendChild(closeButton);
223
- modal.appendChild(imageContainer);
224
- document.body.appendChild(modal);
113
+ }, IMAGE_SETTINGS.FORMAT, IMAGE_SETTINGS.QUALITY);
114
+ });
115
+ }
116
+
117
+ function getCookie(name) {
118
+ var _a;
119
+ if (typeof document === "undefined")
120
+ return null;
121
+ const value = `; ${document.cookie}`;
122
+ const parts = value.split(`; ${name}=`);
123
+ if (parts.length === 2) {
124
+ const part = parts.pop();
125
+ return part ? ((_a = part.split(";").shift()) === null || _a === void 0 ? void 0 : _a.trim()) || null : null;
225
126
  }
226
- /**
227
- * Shows the camera capture modal
228
- * @param ww_access_token - Access token for API
229
- * @param ww_user_id - User identifier
230
- * @param ww_product_id - Product identifier
231
- */
232
- showCameraModal(ww_access_token, ww_user_id, ww_product_id) {
233
- console.log("[WeWear VTO] Opening camera modal...");
234
- // Remove any existing modals first
235
- const existingModals = document.querySelectorAll(`.${CSS_CLASSES.MODAL}`);
236
- existingModals.forEach((modal) => {
127
+ return null;
128
+ }
129
+ function getPositionStyles(position) {
130
+ switch (position) {
131
+ case "bottom-left":
132
+ return "left: 20px; bottom: 20px;";
133
+ case "top-right":
134
+ return "right: 20px; top: 20px;";
135
+ case "top-left":
136
+ return "left: 20px; top: 20px;";
137
+ default:
138
+ return "right: 20px; bottom: 20px;";
139
+ }
140
+ }
141
+ function removeElements(selector) {
142
+ const elements = document.querySelectorAll(selector);
143
+ elements.forEach((element) => {
144
+ element.remove();
145
+ });
146
+ }
147
+
148
+ function createButton(buttonPosition, onClick) {
149
+ const container = document.createElement("div");
150
+ container.className = CSS_CLASSES.BUTTON_CONTAINER;
151
+ const positionStyles = getPositionStyles(buttonPosition);
152
+ container.style.cssText = `
153
+ position: absolute;
154
+ ${positionStyles}
155
+ display: flex;
156
+ border-radius: 50%;
157
+ background: #000000;
158
+ padding: 4px;
159
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
160
+ z-index: ${Z_INDEX.BUTTON};
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 = `
167
+ display: flex;
168
+ width: 36px;
169
+ height: 36px;
170
+ cursor: pointer;
171
+ align-items: center;
172
+ justify-content: center;
173
+ border-radius: 50%;
174
+ padding: 10px;
175
+ border: none;
176
+ background: transparent;
177
+ color: #ffffff;
178
+ `;
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>
181
+ `;
182
+ button.onclick = onClick;
183
+ container.appendChild(button);
184
+ return container;
185
+ }
186
+
187
+ function showCameraModal(callbacks) {
188
+ console.log("[WeWear VTO] Opening camera modal...");
189
+ // Remove any existing modals first
190
+ removeElements(`.${CSS_CLASSES.MODAL}`);
191
+ // Create modal container
192
+ const modal = document.createElement("div");
193
+ modal.className = CSS_CLASSES.MODAL;
194
+ modal.style.cssText = `
195
+ position: fixed;
196
+ top: 0;
197
+ left: 0;
198
+ width: 100%;
199
+ height: 100%;
200
+ background-color: rgba(0, 0, 0, 0.95);
201
+ display: flex;
202
+ flex-direction: column;
203
+ align-items: center;
204
+ justify-content: center;
205
+ z-index: ${Z_INDEX.MODAL};
206
+ padding: 20px;
207
+ box-sizing: border-box;
208
+ `;
209
+ // Create camera container
210
+ const cameraContainer = document.createElement("div");
211
+ cameraContainer.style.cssText = `
212
+ position: relative;
213
+ width: 100%;
214
+ max-width: 500px;
215
+ height: 70vh;
216
+ background-color: #000;
217
+ border-radius: 12px;
218
+ overflow: hidden;
219
+ display: flex;
220
+ flex-direction: column;
221
+ align-items: center;
222
+ justify-content: center;
223
+ `;
224
+ // Create video element
225
+ const video = document.createElement("video");
226
+ video.autoplay = true;
227
+ video.playsInline = true;
228
+ video.muted = true;
229
+ video.style.cssText = `
230
+ width: 100%;
231
+ height: 100%;
232
+ object-fit: cover;
233
+ border-radius: 12px;
234
+ `;
235
+ // Create canvas for capturing
236
+ const canvas = document.createElement("canvas");
237
+ canvas.style.display = "none";
238
+ // Create capture button
239
+ const captureButton = document.createElement("button");
240
+ captureButton.innerHTML = "";
241
+ captureButton.style.cssText = `
242
+ position: absolute;
243
+ bottom: 20px;
244
+ left: 50%;
245
+ transform: translateX(-50%);
246
+ width: 60px;
247
+ height: 60px;
248
+ border: 3px solid white;
249
+ background-color: rgba(0, 0, 0, 0.7);
250
+ border-radius: 50%;
251
+ cursor: pointer;
252
+ display: none;
253
+ align-items: center;
254
+ justify-content: center;
255
+ transition: all 0.2s ease;
256
+ `;
257
+ // Create close button
258
+ const closeButton = document.createElement("button");
259
+ closeButton.innerHTML = "×";
260
+ closeButton.style.cssText = `
261
+ position: absolute;
262
+ top: 15px;
263
+ right: 15px;
264
+ width: 40px;
265
+ height: 40px;
266
+ border: none;
267
+ background-color: rgba(0, 0, 0, 0.7);
268
+ color: white;
269
+ font-size: 24px;
270
+ font-weight: bold;
271
+ cursor: pointer;
272
+ border-radius: 50%;
273
+ display: flex;
274
+ align-items: center;
275
+ justify-content: center;
276
+ `;
277
+ // Create loading indicator
278
+ const loadingIndicator = document.createElement("div");
279
+ loadingIndicator.innerHTML = "Initializing camera...";
280
+ loadingIndicator.style.cssText = `
281
+ color: white;
282
+ font-size: 16px;
283
+ position: absolute;
284
+ top: 50%;
285
+ left: 50%;
286
+ transform: translate(-50%, -50%);
287
+ `;
288
+ // Add event listeners
289
+ closeButton.onclick = () => {
290
+ stopCamera(video);
291
+ modal.remove();
292
+ };
293
+ captureButton.onclick = async () => {
294
+ await callbacks.onCapture(video, canvas);
295
+ };
296
+ // Assemble the camera modal
297
+ cameraContainer.appendChild(video);
298
+ cameraContainer.appendChild(canvas);
299
+ cameraContainer.appendChild(captureButton);
300
+ cameraContainer.appendChild(closeButton);
301
+ cameraContainer.appendChild(loadingIndicator);
302
+ modal.appendChild(cameraContainer);
303
+ document.body.appendChild(modal);
304
+ console.log("[WeWear VTO] Camera modal added to DOM");
305
+ // Start camera
306
+ startCamera(video, loadingIndicator, captureButton);
307
+ }
308
+
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;
317
+ top: 0;
318
+ left: 0;
319
+ width: 100%;
320
+ 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;
339
+ display: flex;
340
+ flex-direction: column;
341
+ align-items: center;
342
+ justify-content: center;
343
+ `;
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;
373
+ `;
374
+ closeButton.onclick = () => {
375
+ modal.remove();
376
+ };
377
+ // Close on backdrop click
378
+ modal.onclick = (e) => {
379
+ if (e.target === modal) {
237
380
  modal.remove();
238
- });
239
- // Create modal container
240
- const modal = document.createElement("div");
241
- modal.className = CSS_CLASSES.MODAL;
242
- modal.style.cssText = `
243
- position: fixed;
244
- top: 0;
245
- left: 0;
246
- width: 100%;
247
- height: 100%;
248
- background-color: rgba(0, 0, 0, 0.95);
249
- display: flex;
250
- flex-direction: column;
251
- align-items: center;
252
- justify-content: center;
253
- z-index: ${Z_INDEX.MODAL};
254
- padding: 20px;
255
- box-sizing: border-box;
256
- `;
257
- // Create camera container
258
- const cameraContainer = document.createElement("div");
259
- cameraContainer.style.cssText = `
260
- position: relative;
261
- width: 100%;
262
- max-width: 500px;
263
- height: 70vh;
264
- background-color: #000;
265
- border-radius: 12px;
266
- overflow: hidden;
267
- display: flex;
268
- flex-direction: column;
269
- align-items: center;
270
- justify-content: center;
271
- `;
272
- // Create video element
273
- const video = document.createElement("video");
274
- video.autoplay = true;
275
- video.playsInline = true;
276
- video.muted = true;
277
- video.style.cssText = `
278
- width: 100%;
279
- height: 100%;
280
- object-fit: cover;
281
- border-radius: 12px;
282
- `;
283
- // Create canvas for capturing
284
- const canvas = document.createElement("canvas");
285
- canvas.style.display = "none";
286
- // Create capture button
287
- const captureButton = document.createElement("button");
288
- captureButton.innerHTML = `
289
- <svg width="24" height="24" viewBox="0 0 24 24" fill="white">
290
- <circle cx="12" cy="12" r="10" stroke="white" stroke-width="2" fill="none"/>
291
- <circle cx="12" cy="12" r="6" fill="white"/>
292
- </svg>
293
- `;
294
- captureButton.style.cssText = `
295
- position: absolute;
296
- bottom: 20px;
297
- left: 50%;
298
- transform: translateX(-50%);
299
- width: 60px;
300
- height: 60px;
301
- border: 3px solid white;
302
- background-color: transparent;
303
- border-radius: 50%;
304
- cursor: pointer;
305
- display: none;
306
- align-items: center;
307
- justify-content: center;
308
- transition: all 0.2s ease;
309
- `;
310
- // Create close button
311
- const closeButton = document.createElement("button");
312
- closeButton.innerHTML = "×";
313
- closeButton.style.cssText = `
314
- position: absolute;
315
- top: 15px;
316
- right: 15px;
317
- width: 40px;
318
- height: 40px;
319
- border: none;
320
- background-color: rgba(0, 0, 0, 0.7);
321
- color: white;
322
- font-size: 24px;
323
- font-weight: bold;
324
- cursor: pointer;
325
- border-radius: 50%;
326
- display: flex;
327
- align-items: center;
328
- justify-content: center;
329
- `;
330
- // Create loading indicator
331
- const loadingIndicator = document.createElement("div");
332
- loadingIndicator.innerHTML = "Initializing camera...";
333
- loadingIndicator.style.cssText = `
334
- color: white;
335
- font-size: 16px;
336
- position: absolute;
337
- top: 50%;
338
- left: 50%;
339
- transform: translate(-50%, -50%);
340
- `;
341
- // Add event listeners
342
- closeButton.onclick = () => {
343
- this.stopCamera(video);
381
+ }
382
+ };
383
+ // Close on Escape key
384
+ const handleEscape = (e) => {
385
+ if (e.key === "Escape") {
344
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);
395
+ }
396
+
397
+ function showReviewModal(imageBlob, callbacks) {
398
+ console.log("[WeWear VTO] Opening review modal...");
399
+ // Remove any existing modals first
400
+ removeElements(`.${CSS_CLASSES.MODAL}`);
401
+ // Create image URL for preview
402
+ const imageUrl = URL.createObjectURL(imageBlob);
403
+ // Create modal container
404
+ const modal = document.createElement("div");
405
+ modal.className = CSS_CLASSES.MODAL;
406
+ modal.style.cssText = `
407
+ position: fixed;
408
+ top: 0;
409
+ left: 0;
410
+ width: 100%;
411
+ height: 100%;
412
+ background-color: rgba(0, 0, 0, 0.95);
413
+ display: flex;
414
+ flex-direction: column;
415
+ align-items: center;
416
+ justify-content: center;
417
+ z-index: ${Z_INDEX.MODAL};
418
+ padding: 20px;
419
+ box-sizing: border-box;
420
+ `;
421
+ // Create review container
422
+ const reviewContainer = document.createElement("div");
423
+ reviewContainer.style.cssText = `
424
+ position: relative;
425
+ width: 100%;
426
+ max-width: 500px;
427
+ height: 70vh;
428
+ background-color: #000;
429
+ border-radius: 12px;
430
+ overflow: hidden;
431
+ display: flex;
432
+ flex-direction: column;
433
+ align-items: center;
434
+ justify-content: center;
435
+ `;
436
+ // Create image element
437
+ const image = document.createElement("img");
438
+ image.src = imageUrl;
439
+ image.alt = "Review your photo";
440
+ image.style.cssText = `
441
+ width: 100%;
442
+ height: 100%;
443
+ object-fit: cover;
444
+ border-radius: 12px;
445
+ `;
446
+ // Create close button
447
+ const closeButton = document.createElement("button");
448
+ closeButton.innerHTML = "×";
449
+ closeButton.style.cssText = `
450
+ position: absolute;
451
+ top: 15px;
452
+ right: 15px;
453
+ width: 40px;
454
+ height: 40px;
455
+ border: none;
456
+ background-color: rgba(0, 0, 0, 0.7);
457
+ color: white;
458
+ font-size: 24px;
459
+ font-weight: bold;
460
+ cursor: pointer;
461
+ border-radius: 50%;
462
+ display: flex;
463
+ align-items: center;
464
+ justify-content: center;
465
+ `;
466
+ // Create button container
467
+ const buttonContainer = document.createElement("div");
468
+ buttonContainer.style.cssText = `
469
+ position: absolute;
470
+ bottom: 20px;
471
+ left: 50%;
472
+ transform: translateX(-50%);
473
+ display: flex;
474
+ gap: 15px;
475
+ width: calc(100% - 40px);
476
+ max-width: 300px;
477
+ `;
478
+ // Create retake button
479
+ const retakeButton = document.createElement("button");
480
+ retakeButton.textContent = "Retake";
481
+ retakeButton.style.cssText = `
482
+ flex: 1;
483
+ padding: 12px 24px;
484
+ background-color: rgba(0, 0, 0, 0.7);
485
+ color: white;
486
+ border-radius: 8px;
487
+ border: none;
488
+ font-size: 16px;
489
+ font-weight: 600;
490
+ cursor: pointer;
491
+ transition: all 0.2s ease;
492
+ `;
493
+ // Create use photo button
494
+ const usePhotoButton = document.createElement("button");
495
+ usePhotoButton.textContent = "Use Photo";
496
+ usePhotoButton.style.cssText = `
497
+ flex: 1;
498
+ padding: 12px 24px;
499
+ background-color: rgba(0, 0, 0, 0.7);
500
+ color: white;
501
+ border-radius: 8px;
502
+ border: none;
503
+ font-size: 16px;
504
+ font-weight: 600;
505
+ cursor: pointer;
506
+ transition: all 0.2s ease;
507
+ `;
508
+ // Add event listeners
509
+ closeButton.onclick = () => {
510
+ URL.revokeObjectURL(imageUrl); // Clean up
511
+ modal.remove();
512
+ };
513
+ retakeButton.onclick = () => {
514
+ URL.revokeObjectURL(imageUrl); // Clean up
515
+ modal.remove();
516
+ callbacks.onRetake();
517
+ };
518
+ usePhotoButton.onclick = async () => {
519
+ URL.revokeObjectURL(imageUrl); // Clean up
520
+ modal.remove();
521
+ await callbacks.onAccept(imageBlob);
522
+ };
523
+ // Assemble the review modal
524
+ buttonContainer.appendChild(retakeButton);
525
+ buttonContainer.appendChild(usePhotoButton);
526
+ reviewContainer.appendChild(image);
527
+ reviewContainer.appendChild(closeButton);
528
+ reviewContainer.appendChild(buttonContainer);
529
+ modal.appendChild(reviewContainer);
530
+ document.body.appendChild(modal);
531
+ console.log("[WeWear VTO] Review modal added to DOM");
532
+ }
533
+
534
+ class VirtualTryOnWidget {
535
+ constructor(config = {}) {
536
+ this.config = {
537
+ baseUrl: config.baseUrl || DEFAULT_CONFIG.BASE_URL,
538
+ productPageSelector: config.productPageSelector || DEFAULT_CONFIG.PRODUCT_PAGE_SELECTOR,
539
+ gallerySelector: config.gallerySelector || DEFAULT_CONFIG.GALLERY_SELECTOR,
540
+ skuSelector: config.skuSelector || DEFAULT_CONFIG.SKU_SELECTOR,
541
+ buttonPosition: config.buttonPosition || DEFAULT_CONFIG.BUTTON_POSITION,
345
542
  };
346
- captureButton.onclick = () => {
347
- this.captureImage(video, canvas, ww_access_token, ww_user_id, ww_product_id, modal);
348
- };
349
- // Assemble the camera modal
350
- cameraContainer.appendChild(video);
351
- cameraContainer.appendChild(canvas);
352
- cameraContainer.appendChild(captureButton);
353
- cameraContainer.appendChild(closeButton);
354
- cameraContainer.appendChild(loadingIndicator);
355
- modal.appendChild(cameraContainer);
356
- document.body.appendChild(modal);
357
- console.log("[WeWear VTO] Camera modal added to DOM");
358
- // Start camera
359
- this.startCamera(video, loadingIndicator, captureButton);
360
- } /**
543
+ }
544
+ /**
361
545
  * Initializes the virtual try-on widget on the current page
362
546
  * @returns Promise that resolves when initialization is complete
363
547
  */
@@ -378,7 +562,7 @@
378
562
  container.style.position = "relative";
379
563
  }
380
564
  // Create and add the virtual try-on button
381
- const button = this.createButton(async () => {
565
+ const button = createButton(this.config.buttonPosition, async () => {
382
566
  await this.handleTryOnClick();
383
567
  });
384
568
  container.appendChild(button);
@@ -397,308 +581,84 @@
397
581
  console.log("[WeWear VTO] Button clicked, starting try-on process...");
398
582
  try {
399
583
  // Get required data
400
- const ww_access_token = this.getCookie("ww_access_token");
401
- const ww_user_id = this.getCookie("ww_user_id");
584
+ const ww_access_token = getCookie("ww_access_token");
585
+ const ww_user_id = getCookie("ww_user_id");
402
586
  const skuElement = document.querySelector(this.config.skuSelector);
403
587
  const ww_product_id = (_a = skuElement === null || skuElement === void 0 ? void 0 : skuElement.textContent) === null || _a === void 0 ? void 0 : _a.trim();
404
588
  console.log("[WeWear VTO] Retrieved data:", {
405
589
  ww_user_id,
406
590
  ww_product_id,
407
- ww_access_token: !!ww_access_token,
591
+ ww_access_token,
408
592
  });
409
593
  // Validate required data
410
594
  if (!ww_user_id) {
411
595
  console.warn("[WeWear VTO] Missing required cookie: ww_user_id");
412
- this.showError("Please sign in to use virtual try-on");
413
596
  return;
414
597
  }
415
598
  if (!ww_product_id) {
416
599
  console.warn("[WeWear VTO] Product SKU not found:", this.config.skuSelector);
417
- this.showError("Product information not available");
600
+ return;
601
+ }
602
+ if (!ww_access_token) {
603
+ console.warn("[WeWear VTO] Missing required cookie: ww_access_token");
418
604
  return;
419
605
  }
420
606
  // Show camera capture modal
421
- this.showCameraModal(ww_access_token, ww_user_id, ww_product_id);
607
+ this.showCameraModalWithCallbacks(ww_access_token, ww_user_id, ww_product_id);
422
608
  }
423
609
  catch (error) {
424
610
  console.error("[WeWear VTO] Try-on request failed:", error);
425
- this.showError("An unexpected error occurred. Please try again.");
426
611
  }
427
612
  }
428
613
  /**
429
- * Shows an error message to the user
430
- * @param message - Error message to display
614
+ * Shows camera modal with appropriate callbacks
431
615
  * @private
432
616
  */
433
- showError(message) {
434
- // Simple alert for now - could be enhanced with a custom modal
435
- alert(`WeWear Virtual Try-On: ${message}`);
436
- }
437
- /**
438
- * Starts the camera stream
439
- * @param video - Video element to display camera stream
440
- * @param loadingIndicator - Loading indicator element
441
- * @param captureButton - Capture button element
442
- */
443
- async startCamera(video, loadingIndicator, captureButton) {
444
- console.log("[WeWear VTO] Starting camera...");
445
- try {
446
- const constraints = {
447
- video: {
448
- width: { ideal: 1280 },
449
- height: { ideal: 720 },
450
- facingMode: "user", // Front-facing camera
451
- },
452
- audio: false,
453
- };
454
- console.log("[WeWear VTO] Requesting camera access...");
455
- const stream = await navigator.mediaDevices.getUserMedia(constraints);
456
- video.srcObject = stream;
457
- video.onloadedmetadata = () => {
458
- console.log("[WeWear VTO] Camera stream loaded successfully");
459
- loadingIndicator.style.display = "none";
460
- captureButton.style.display = "flex";
461
- };
462
- }
463
- catch (error) {
464
- console.error("[WeWear VTO] Camera access error:", error);
465
- loadingIndicator.innerHTML =
466
- "Camera access denied. Please allow camera permissions.";
467
- }
468
- }
469
- /**
470
- * Stops the camera stream
471
- * @param video - Video element with camera stream
472
- */
473
- stopCamera(video) {
474
- const stream = video.srcObject;
475
- if (stream) {
476
- const tracks = stream.getTracks();
477
- tracks.forEach((track) => {
478
- track.stop();
479
- });
480
- video.srcObject = null;
481
- }
482
- }
483
- /**
484
- * Captures an image from the video stream
485
- * @param video - Video element with camera stream
486
- * @param canvas - Canvas element for capturing
487
- * @param ww_access_token - Access token for API
488
- * @param ww_user_id - User identifier
489
- * @param ww_product_id - Product identifier
490
- * @param modal - Modal element to close after processing
491
- */
492
- async captureImage(video, canvas, ww_access_token, ww_user_id, ww_product_id, modal) {
493
- try {
494
- // Set canvas dimensions to match video
495
- canvas.width = video.videoWidth;
496
- canvas.height = video.videoHeight;
497
- // Draw video frame to canvas
498
- const ctx = canvas.getContext("2d");
499
- if (!ctx) {
500
- throw new Error("Could not get canvas context");
501
- }
502
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
503
- // Convert canvas to blob
504
- const blob = await new Promise((resolve, reject) => {
505
- canvas.toBlob((blob) => {
506
- if (blob) {
507
- resolve(blob);
508
- }
509
- else {
510
- reject(new Error("Failed to create blob from canvas"));
511
- }
512
- }, "image/jpeg", 0.8);
513
- });
514
- // Stop camera and close camera modal
515
- this.stopCamera(video);
516
- modal.remove();
517
- // Show review modal instead of immediately processing
518
- this.showReviewModal(blob, ww_access_token, ww_user_id, ww_product_id);
519
- }
520
- catch (error) {
521
- console.error("[WeWear VTO] Image capture error:", error);
522
- this.showError("Failed to capture image. Please try again.");
523
- }
617
+ showCameraModalWithCallbacks(ww_access_token, ww_user_id, ww_product_id) {
618
+ const callbacks = {
619
+ onCapture: async (video, canvas) => {
620
+ try {
621
+ // Capture image from video
622
+ const imageBlob = await captureImageFromVideo(video, canvas);
623
+ // Stop camera and show review modal
624
+ stopCamera(video);
625
+ // Show review modal instead of immediately processing
626
+ this.showReviewModalWithCallbacks(imageBlob, ww_access_token, ww_user_id, ww_product_id);
627
+ }
628
+ catch (error) {
629
+ console.error("[WeWear VTO] Image capture error:", error);
630
+ }
631
+ },
632
+ };
633
+ showCameraModal(callbacks);
524
634
  }
525
635
  /**
526
- * Shows the review modal for captured image
527
- * @param imageBlob - Captured image blob
528
- * @param ww_access_token - Access token for API
529
- * @param ww_user_id - User identifier
530
- * @param ww_product_id - Product identifier
636
+ * Shows review modal with appropriate callbacks
637
+ * @private
531
638
  */
532
- showReviewModal(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
533
- console.log("[WeWear VTO] Opening review modal...");
534
- // Remove any existing modals first
535
- const existingModals = document.querySelectorAll(`.${CSS_CLASSES.MODAL}`);
536
- existingModals.forEach((modal) => {
537
- modal.remove();
538
- });
539
- // Create image URL for preview
540
- const imageUrl = URL.createObjectURL(imageBlob);
541
- // Create modal container (same as camera modal)
542
- const modal = document.createElement("div");
543
- modal.className = CSS_CLASSES.MODAL;
544
- modal.style.cssText = `
545
- position: fixed;
546
- top: 0;
547
- left: 0;
548
- width: 100%;
549
- height: 100%;
550
- background-color: rgba(0, 0, 0, 0.95);
551
- display: flex;
552
- flex-direction: column;
553
- align-items: center;
554
- justify-content: center;
555
- z-index: ${Z_INDEX.MODAL};
556
- padding: 20px;
557
- box-sizing: border-box;
558
- `;
559
- // Create review container (similar to camera container)
560
- const reviewContainer = document.createElement("div");
561
- reviewContainer.style.cssText = `
562
- position: relative;
563
- width: 100%;
564
- max-width: 500px;
565
- height: 70vh;
566
- background-color: #000;
567
- border-radius: 12px;
568
- overflow: hidden;
569
- display: flex;
570
- flex-direction: column;
571
- align-items: center;
572
- justify-content: center;
573
- `;
574
- // Create image element (replaces video element)
575
- const image = document.createElement("img");
576
- image.src = imageUrl;
577
- image.alt = "Review your photo";
578
- image.style.cssText = `
579
- width: 100%;
580
- height: 100%;
581
- object-fit: cover;
582
- border-radius: 12px;
583
- `;
584
- // Create close button (same as camera modal)
585
- const closeButton = document.createElement("button");
586
- closeButton.innerHTML = "×";
587
- closeButton.style.cssText = `
588
- position: absolute;
589
- top: 15px;
590
- right: 15px;
591
- width: 40px;
592
- height: 40px;
593
- border: none;
594
- background-color: rgba(0, 0, 0, 0.7);
595
- color: white;
596
- font-size: 24px;
597
- font-weight: bold;
598
- cursor: pointer;
599
- border-radius: 50%;
600
- display: flex;
601
- align-items: center;
602
- justify-content: center;
603
- `;
604
- // Create button container positioned at bottom (similar to capture button position)
605
- const buttonContainer = document.createElement("div");
606
- buttonContainer.style.cssText = `
607
- position: absolute;
608
- bottom: 20px;
609
- left: 50%;
610
- transform: translateX(-50%);
611
- display: flex;
612
- gap: 15px;
613
- width: calc(100% - 40px);
614
- max-width: 300px;
615
- `;
616
- // Create retake button
617
- const retakeButton = document.createElement("button");
618
- retakeButton.textContent = "Retake";
619
- retakeButton.style.cssText = `
620
- flex: 1;
621
- padding: 12px 24px;
622
- background-color: transparent;
623
- color: white;
624
- border: 2px solid white;
625
- border-radius: 8px;
626
- font-size: 16px;
627
- font-weight: 600;
628
- cursor: pointer;
629
- transition: all 0.2s ease;
630
- `;
631
- // Create use photo button
632
- const usePhotoButton = document.createElement("button");
633
- usePhotoButton.textContent = "Use Photo";
634
- usePhotoButton.style.cssText = `
635
- flex: 1;
636
- padding: 12px 24px;
637
- background-color: white;
638
- color: black;
639
- border: 2px solid white;
640
- border-radius: 8px;
641
- font-size: 16px;
642
- font-weight: 600;
643
- cursor: pointer;
644
- transition: all 0.2s ease;
645
- `;
646
- // Add hover effects
647
- retakeButton.addEventListener("mouseenter", () => {
648
- retakeButton.style.backgroundColor = "white";
649
- retakeButton.style.color = "black";
650
- });
651
- retakeButton.addEventListener("mouseleave", () => {
652
- retakeButton.style.backgroundColor = "transparent";
653
- retakeButton.style.color = "white";
654
- });
655
- usePhotoButton.addEventListener("mouseenter", () => {
656
- usePhotoButton.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
657
- });
658
- usePhotoButton.addEventListener("mouseleave", () => {
659
- usePhotoButton.style.backgroundColor = "white";
660
- });
661
- // Add event listeners
662
- closeButton.onclick = () => {
663
- URL.revokeObjectURL(imageUrl); // Clean up
664
- modal.remove();
665
- };
666
- retakeButton.onclick = () => {
667
- URL.revokeObjectURL(imageUrl); // Clean up
668
- modal.remove();
669
- // Reopen camera modal
670
- this.showCameraModal(ww_access_token, ww_user_id, ww_product_id);
671
- };
672
- usePhotoButton.onclick = async () => {
673
- URL.revokeObjectURL(imageUrl); // Clean up
674
- modal.remove();
675
- // Process the image
676
- await this.processAcceptedImage(imageBlob, ww_access_token, ww_user_id, ww_product_id);
639
+ showReviewModalWithCallbacks(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
640
+ const callbacks = {
641
+ onRetake: () => {
642
+ // Reopen camera modal
643
+ this.showCameraModalWithCallbacks(ww_access_token, ww_user_id, ww_product_id);
644
+ },
645
+ onAccept: async (acceptedImageBlob) => {
646
+ await this.processAcceptedImage(acceptedImageBlob, ww_access_token, ww_user_id, ww_product_id);
647
+ },
677
648
  };
678
- // Assemble the review modal (similar to camera modal)
679
- buttonContainer.appendChild(retakeButton);
680
- buttonContainer.appendChild(usePhotoButton);
681
- reviewContainer.appendChild(image);
682
- reviewContainer.appendChild(closeButton);
683
- reviewContainer.appendChild(buttonContainer);
684
- modal.appendChild(reviewContainer);
685
- document.body.appendChild(modal);
686
- console.log("[WeWear VTO] Review modal added to DOM");
649
+ showReviewModal(imageBlob, callbacks);
687
650
  }
688
651
  /**
689
652
  * Processes the accepted image by calling the API
690
- * @param imageBlob - Accepted image blob
691
- * @param ww_access_token - Access token for API
692
- * @param ww_user_id - User identifier
693
- * @param ww_product_id - Product identifier
653
+ * @private
694
654
  */
695
655
  async processAcceptedImage(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
696
656
  try {
697
657
  console.log("[WeWear VTO] Processing accepted image...");
698
658
  // Call the API with the accepted image
699
- const result = await this.callVirtualTryOnApiWithImage(ww_access_token, ww_user_id, ww_product_id, imageBlob);
659
+ const result = await callVirtualTryOnApi(this.config.baseUrl, ww_access_token, ww_user_id, ww_product_id, imageBlob);
700
660
  if (result === null || result === void 0 ? void 0 : result.imageUrl) {
701
- this.showImageModal(result.imageUrl);
661
+ showImageModal(result.imageUrl);
702
662
  }
703
663
  else {
704
664
  console.error("[WeWear VTO] Invalid API response:", result);
@@ -706,42 +666,6 @@
706
666
  }
707
667
  catch (error) {
708
668
  console.error("[WeWear VTO] Error processing accepted image:", error);
709
- this.showError("Failed to process image. Please try again.");
710
- }
711
- }
712
- /**
713
- * Makes API call to virtual try-on service with captured image
714
- * @param ww_access_token - Optional authentication token
715
- * @param ww_user_id - User identifier
716
- * @param ww_product_id - Product identifier
717
- * @param imageBlob - Captured image blob
718
- * @returns Promise with virtual try-on result or null if failed
719
- */
720
- async callVirtualTryOnApiWithImage(ww_access_token, ww_user_id, ww_product_id, imageBlob) {
721
- try {
722
- // Create form data for multipart upload
723
- const formData = new FormData();
724
- formData.append("ww_user_id", ww_user_id);
725
- formData.append("ww_product_id", ww_product_id);
726
- formData.append("image", imageBlob, "captured-image.jpg");
727
- if (ww_access_token) {
728
- formData.append("ww_access_token", ww_access_token);
729
- }
730
- const response = await fetch(`${this.config.baseUrl}/api/virtual-try-on`, {
731
- method: "POST",
732
- body: formData,
733
- });
734
- if (!response.ok) {
735
- console.error("[WeWear VTO] API request failed:", response.status, response.statusText);
736
- return null;
737
- }
738
- const result = await response.json();
739
- console.log("[WeWear VTO] API response:", result);
740
- return result;
741
- }
742
- catch (error) {
743
- console.error("[WeWear VTO] API call failed:", error);
744
- return null;
745
669
  }
746
670
  }
747
671
  /**
@@ -750,15 +674,9 @@
750
674
  destroy() {
751
675
  try {
752
676
  // Remove all buttons
753
- const buttons = document.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
754
- buttons.forEach((button) => {
755
- button.remove();
756
- });
677
+ removeElements(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
757
678
  // Remove all modals
758
- const modals = document.querySelectorAll(`.${CSS_CLASSES.MODAL}`);
759
- modals.forEach((modal) => {
760
- modal.remove();
761
- });
679
+ removeElements(`.${CSS_CLASSES.MODAL}`);
762
680
  console.log("[WeWear VTO] Widget destroyed successfully");
763
681
  }
764
682
  catch (error) {
@@ -767,17 +685,7 @@
767
685
  }
768
686
  }
769
687
 
770
- /**
771
- * WeWear Virtual Try-On Widget Auto-Installer
772
- *
773
- * Provides automatic initialization and management of the virtual try-on widget.
774
- * Handles DOM ready states and widget lifecycle management.
775
- */
776
688
  let widgetInstance = null;
777
- /**
778
- * Initializes the virtual try-on widget with optional configuration
779
- * @param config - Widget configuration options
780
- */
781
689
  function initVirtualTryOn(config) {
782
690
  try {
783
691
  // Clean up any existing instance
@@ -805,9 +713,6 @@
805
713
  console.error("[WeWear VTO] Initialization error:", error);
806
714
  }
807
715
  }
808
- /**
809
- * Destroys the current widget instance and cleans up resources
810
- */
811
716
  function destroyVirtualTryOn() {
812
717
  try {
813
718
  if (widgetInstance) {
@@ -820,13 +725,16 @@
820
725
  console.error("[WeWear VTO] Error destroying widget:", error);
821
726
  }
822
727
  }
823
- // Auto-initialize if window object exists (browser environment)
728
+ function getWidgetInstance() {
729
+ return widgetInstance;
730
+ }
824
731
  if (typeof window !== "undefined") {
825
732
  initVirtualTryOn();
826
733
  }
827
734
 
828
735
  exports.VirtualTryOnWidget = VirtualTryOnWidget;
829
736
  exports.destroyVirtualTryOn = destroyVirtualTryOn;
737
+ exports.getWidgetInstance = getWidgetInstance;
830
738
  exports.initVirtualTryOn = initVirtualTryOn;
831
739
 
832
740
  }));