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