@primestyleai/tryon 1.0.1 → 1.1.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/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "1.0.1",
4
- "description": "PrimeStyle Virtual Try-On Web Component SDK",
3
+ "version": "1.1.0",
4
+ "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
- "main": "dist/primestyle-tryon.umd.js",
7
- "module": "dist/primestyle-tryon.es.js",
6
+ "main": "dist/primestyle-tryon.js",
7
+ "module": "dist/primestyle-tryon.js",
8
8
  "types": "dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "import": "./dist/primestyle-tryon.es.js",
12
- "require": "./dist/primestyle-tryon.umd.js",
11
+ "import": "./dist/primestyle-tryon.js",
13
12
  "types": "./dist/index.d.ts"
13
+ },
14
+ "./react": {
15
+ "import": "./dist/react/index.js",
16
+ "types": "./dist/react/index.d.ts"
14
17
  }
15
18
  },
16
19
  "files": [
@@ -24,6 +27,7 @@
24
27
  },
25
28
  "keywords": [
26
29
  "virtual-try-on",
30
+ "react",
27
31
  "web-component",
28
32
  "fashion",
29
33
  "ai",
@@ -31,9 +35,22 @@
31
35
  ],
32
36
  "author": "PrimeStyle AI",
33
37
  "license": "MIT",
38
+ "peerDependencies": {
39
+ "react": ">=18.0.0",
40
+ "react-dom": ">=18.0.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "react": {
44
+ "optional": true
45
+ },
46
+ "react-dom": {
47
+ "optional": true
48
+ }
49
+ },
34
50
  "devDependencies": {
51
+ "@types/react": "^18.3.28",
52
+ "terser": "^5.31.0",
35
53
  "typescript": "^5.5.0",
36
- "vite": "^5.4.0",
37
- "terser": "^5.31.0"
54
+ "vite": "^5.4.0"
38
55
  }
39
56
  }
@@ -1 +0,0 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).PrimeStyleTryon={})}(this,function(e){"use strict";class t{constructor(e,t){this.apiKey=e,this.baseUrl=(t||"https://api.primestyleai.com").replace(/\/+$/,"")}get headers(){return{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}}async submitTryOn(e,t){const s=await fetch(`${this.baseUrl}/api/v1/tryon`,{method:"POST",headers:this.headers,body:JSON.stringify({modelImage:e,garmentImage:t})});if(!s.ok){const e=await s.json().catch(()=>({}));if(402===s.status)throw new n(e.message||"Insufficient tokens","INSUFFICIENT_TOKENS");throw new n(e.message||"Failed to submit try-on","API_ERROR")}return s.json()}async getStatus(e){const t=await fetch(`${this.baseUrl}/api/v1/tryon/status/${e}`,{headers:this.headers});if(!t.ok){const e=await t.json().catch(()=>({}));throw new n(e.message||"Failed to get status","API_ERROR")}return t.json()}getStreamUrl(){return`${this.baseUrl}/api/v1/tryon/stream?key=${encodeURIComponent(this.apiKey)}`}}class n extends Error{constructor(e,t){super(e),this.name="PrimeStyleError",this.code=t}}class s{constructor(e){this.eventSource=null,this.listeners=new Map,this.reconnectTimer=null,this.reconnectAttempts=0,this.maxReconnectAttempts=5,this.streamUrl=e}connect(){this.eventSource||(this.eventSource=new EventSource(this.streamUrl),this.eventSource.addEventListener("vto-update",e=>{try{const t=JSON.parse(e.data);this.emit(t.galleryId,t)}catch{}}),this.eventSource.onopen=()=>{this.reconnectAttempts=0},this.eventSource.onerror=()=>{this.eventSource?.close(),this.eventSource=null,this.scheduleReconnect()})}scheduleReconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts)return;if(0===this.listeners.size)return;const e=Math.min(1e3*2**this.reconnectAttempts,3e4);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{this.connect()},e)}onJob(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),this.eventSource||this.connect(),()=>{const n=this.listeners.get(e);n&&(n.delete(t),0===n.size&&this.listeners.delete(e)),0===this.listeners.size&&this.disconnect()}}emit(e,t){const n=this.listeners.get(e);n&&n.forEach(e=>e(t))}disconnect(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.listeners.clear(),this.reconnectAttempts=0}}function r(){const e=document.querySelector('meta[property="og:image"]');if(e?.content)return e.content;const t=document.querySelectorAll('script[type="application/ld+json"]');for(const s of t)try{const e=o(JSON.parse(s.textContent||""));if(e)return e}catch{}const n=["[data-product-image] img","[data-product-image]",".product-image img",".product-gallery img","#product-image",".product__media img",".product-single__photo img",".woocommerce-product-gallery img",".product-media img"];for(const s of n){const e=document.querySelector(s);if(e){const t=e.src||e.dataset.src||e.dataset.zoom;if(t)return t}}return null}function o(e){if(!e||"object"!=typeof e)return null;if(Array.isArray(e)){for(const t of e){const e=o(t);if(e)return e}return null}const t=e;if("Product"===t["@type"]||"IndividualProduct"===t["@type"]){const e=t.image;if("string"==typeof e)return e;if(Array.isArray(e)&&"string"==typeof e[0])return e[0];if(e&&"object"==typeof e){const t=e;if("string"==typeof t.url)return t.url;if("string"==typeof t.contentUrl)return t.contentUrl}}return Array.isArray(t["@graph"])?o(t["@graph"]):null}const i=1024;function a(e){return new Promise((t,n)=>{const s=new FileReader;s.onload=()=>{const e=new Image;e.onload=()=>{try{const s=document.createElement("canvas");let{width:r,height:o}=e;(r>i||o>i)&&(r>o?(o=Math.round(o*i/r),r=i):(r=Math.round(r*i/o),o=i)),s.width=r,s.height=o;const a=s.getContext("2d");if(!a)return void n(new Error("Canvas context not available"));a.drawImage(e,0,0,r,o);const l=s.toDataURL("image/jpeg",.85);t(l)}catch(s){n(s)}},e.onerror=()=>n(new Error("Failed to load image")),e.src=s.result},s.onerror=()=>n(new Error("Failed to read file")),s.readAsDataURL(e)})}function l(e){return["image/jpeg","image/png","image/webp"].includes(e.type)}const p={camera:'<svg viewBox="0 0 24 24"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>',upload:'<svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>',x:'<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',alert:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>'};class d extends HTMLElement{constructor(){super(),this.apiClient=null,this.sseClient=null,this.sseUnsubscribe=null,this.state="idle",this.selectedFile=null,this.previewUrl=null,this.resultImageUrl=null,this.errorMessage=null,this.currentJobId=null,this.productImageUrl=null,this.buttonStyles={},this.modalStyles={},this.shadow=this.attachShadow({mode:"open"})}static get observedAttributes(){return["api-key","api-url","product-image","button-text","locale","show-powered-by","button-styles","modal-styles"]}connectedCallback(){this.init(),this.render()}disconnectedCallback(){this.cleanup()}attributeChangedCallback(e,t,n){if("api-key"!==e&&"api-url"!==e||this.initApi(),"product-image"===e&&(this.productImageUrl=n),"button-styles"===e)try{this.buttonStyles=JSON.parse(n)}catch{}if("modal-styles"===e)try{this.modalStyles=JSON.parse(n)}catch{}this.isConnected&&this.render()}setButtonStyles(e){this.buttonStyles={...this.buttonStyles,...e},this.applyButtonStyles()}setModalStyles(e){this.modalStyles={...this.modalStyles,...e},this.applyModalStyles()}open(){this.state="upload",this.render(),this.emit("ps:open")}close(){this.state="idle",this.resetUpload(),this.render(),this.emit("ps:close")}detectProduct(){const e=r();return e&&(this.productImageUrl=e,this.emit("ps:product-detected",{imageUrl:e})),e}init(){const e=this.getAttribute("button-styles");if(e)try{this.buttonStyles=JSON.parse(e)}catch{}const t=this.getAttribute("modal-styles");if(t)try{this.modalStyles=JSON.parse(t)}catch{}this.productImageUrl=this.getAttribute("product-image")||null,this.productImageUrl||(this.productImageUrl=r(),this.productImageUrl&&this.emit("ps:product-detected",{imageUrl:this.productImageUrl})),this.initApi()}initApi(){const e=this.getAttribute("api-key");if(!e)return;const n=this.getAttribute("api-url")||void 0;this.apiClient=new t(e,n),this.sseClient=new s(this.apiClient.getStreamUrl())}cleanup(){this.sseUnsubscribe&&(this.sseUnsubscribe(),this.sseUnsubscribe=null),this.sseClient&&(this.sseClient.disconnect(),this.sseClient=null),this.previewUrl&&URL.revokeObjectURL(this.previewUrl)}emit(e,t){this.dispatchEvent(new CustomEvent(e,{bubbles:!0,composed:!0,detail:t}))}get buttonText(){return this.getAttribute("button-text")||"Virtual Try-On"}get showPoweredBy(){return"false"!==this.getAttribute("show-powered-by")}render(){this.shadow.innerHTML="";const e=document.createElement("style");e.textContent="\n :host {\n display: inline-block;\n --ps-primary: #bb945c;\n --ps-primary-hover: #a07d4e;\n --ps-text: #ffffff;\n --ps-text-secondary: #999999;\n --ps-bg: #111211;\n --ps-bg-secondary: #1a1b1a;\n --ps-border: #333333;\n --ps-radius: 12px;\n --ps-font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --ps-overlay: rgba(0, 0, 0, 0.6);\n --ps-error: #ef4444;\n --ps-success: #22c55e;\n }\n\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n /* ── Button ─────────────────────────────── */\n .ps-button {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: var(--ps-btn-padding, 12px 24px);\n background: var(--ps-btn-bg, var(--ps-primary));\n color: var(--ps-btn-color, #111211);\n font-family: var(--ps-btn-font, var(--ps-font));\n font-size: var(--ps-btn-font-size, 14px);\n font-weight: var(--ps-btn-font-weight, 600);\n border: var(--ps-btn-border, none);\n border-radius: var(--ps-btn-radius, 8px);\n cursor: pointer;\n transition: all 0.2s ease;\n width: var(--ps-btn-width, auto);\n height: var(--ps-btn-height, auto);\n box-shadow: var(--ps-btn-shadow, none);\n line-height: 1;\n white-space: nowrap;\n }\n\n .ps-button:hover {\n background: var(--ps-btn-hover-bg, var(--ps-primary-hover));\n color: var(--ps-btn-hover-color, var(--ps-btn-color, #111211));\n transform: translateY(-1px);\n }\n\n .ps-button:active {\n transform: translateY(0);\n }\n\n .ps-button svg {\n width: var(--ps-btn-icon-size, 18px);\n height: var(--ps-btn-icon-size, 18px);\n fill: none;\n stroke: var(--ps-btn-icon-color, currentColor);\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n }\n\n /* ── Modal Overlay ──────────────────────── */\n .ps-overlay {\n position: fixed;\n inset: 0;\n background: var(--ps-modal-overlay, var(--ps-overlay));\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999999;\n opacity: 0;\n visibility: hidden;\n transition: opacity 0.25s ease, visibility 0.25s ease;\n padding: 16px;\n }\n\n .ps-overlay.ps-open {\n opacity: 1;\n visibility: visible;\n }\n\n /* ── Modal ──────────────────────────────── */\n .ps-modal {\n background: var(--ps-modal-bg, var(--ps-bg));\n color: var(--ps-modal-color, var(--ps-text));\n border-radius: var(--ps-modal-radius, var(--ps-radius));\n width: var(--ps-modal-width, 100%);\n max-width: var(--ps-modal-max-width, 480px);\n max-height: 90vh;\n overflow-y: auto;\n font-family: var(--ps-modal-font, var(--ps-font));\n position: relative;\n transform: translateY(20px) scale(0.97);\n transition: transform 0.25s ease;\n box-shadow: 0 25px 50px rgba(0, 0, 0, 0.4);\n }\n\n .ps-open .ps-modal {\n transform: translateY(0) scale(1);\n }\n\n /* ── Modal Header ───────────────────────── */\n .ps-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n background: var(--ps-modal-header-bg, var(--ps-bg-secondary));\n border-bottom: 1px solid var(--ps-border);\n border-radius: var(--ps-modal-radius, var(--ps-radius)) var(--ps-modal-radius, var(--ps-radius)) 0 0;\n }\n\n .ps-header-title {\n font-size: 16px;\n font-weight: 600;\n color: var(--ps-modal-header-color, var(--ps-text));\n }\n\n .ps-close {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: none;\n border: none;\n color: var(--ps-modal-close-color, var(--ps-text-secondary));\n cursor: pointer;\n border-radius: 6px;\n transition: background 0.15s;\n }\n\n .ps-close:hover {\n background: rgba(255, 255, 255, 0.1);\n }\n\n .ps-close svg {\n width: 20px;\n height: 20px;\n stroke: currentColor;\n stroke-width: 2;\n fill: none;\n }\n\n /* ── Modal Body ─────────────────────────── */\n .ps-body {\n padding: 24px;\n }\n\n /* ── Upload Zone ─────────────────────────── */\n .ps-upload-zone {\n border: 2px dashed var(--ps-upload-border, var(--ps-border));\n border-radius: var(--ps-radius);\n padding: 40px 24px;\n text-align: center;\n cursor: pointer;\n transition: all 0.2s ease;\n background: var(--ps-upload-bg, transparent);\n }\n\n .ps-upload-zone:hover,\n .ps-upload-zone.ps-drag-over {\n border-color: var(--ps-primary);\n background: rgba(187, 148, 92, 0.05);\n }\n\n .ps-upload-zone input {\n display: none;\n }\n\n .ps-upload-icon {\n width: 48px;\n height: 48px;\n margin: 0 auto 12px;\n stroke: var(--ps-upload-icon-color, var(--ps-primary));\n fill: none;\n stroke-width: 1.5;\n }\n\n .ps-upload-text {\n font-size: 14px;\n color: var(--ps-upload-color, var(--ps-text));\n margin-bottom: 4px;\n }\n\n .ps-upload-hint {\n font-size: 12px;\n color: var(--ps-text-secondary);\n }\n\n /* ── Preview ────────────────────────────── */\n .ps-preview {\n margin-top: 16px;\n position: relative;\n }\n\n .ps-preview img {\n width: 100%;\n border-radius: var(--ps-radius);\n display: block;\n }\n\n .ps-preview-remove {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.6);\n border: none;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n transition: background 0.15s;\n }\n\n .ps-preview-remove:hover {\n background: rgba(0, 0, 0, 0.8);\n }\n\n /* ── Submit Button ──────────────────────── */\n .ps-submit {\n width: 100%;\n padding: 14px;\n margin-top: 20px;\n background: var(--ps-modal-primary-bg, var(--ps-primary));\n color: var(--ps-modal-primary-color, #111211);\n font-family: var(--ps-modal-font, var(--ps-font));\n font-size: 14px;\n font-weight: 600;\n border: none;\n border-radius: var(--ps-modal-primary-radius, 8px);\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n }\n\n .ps-submit:hover:not(:disabled) {\n opacity: 0.9;\n transform: translateY(-1px);\n }\n\n .ps-submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n /* ── Processing State ───────────────────── */\n .ps-processing {\n text-align: center;\n padding: 40px 24px;\n }\n\n .ps-spinner {\n width: 48px;\n height: 48px;\n border: 3px solid var(--ps-border);\n border-top-color: var(--ps-loader, var(--ps-primary));\n border-radius: 50%;\n animation: ps-spin 0.8s linear infinite;\n margin: 0 auto 16px;\n }\n\n @keyframes ps-spin {\n to { transform: rotate(360deg); }\n }\n\n .ps-processing-text {\n font-size: 14px;\n color: var(--ps-text);\n margin-bottom: 4px;\n }\n\n .ps-processing-sub {\n font-size: 12px;\n color: var(--ps-text-secondary);\n }\n\n /* ── Result ─────────────────────────────── */\n .ps-result {\n text-align: center;\n }\n\n .ps-result img {\n width: 100%;\n border-radius: var(--ps-result-radius, var(--ps-radius));\n display: block;\n margin-bottom: 16px;\n }\n\n .ps-result-actions {\n display: flex;\n gap: 8px;\n }\n\n .ps-result-actions button {\n flex: 1;\n padding: 12px;\n font-family: var(--ps-modal-font, var(--ps-font));\n font-size: 13px;\n font-weight: 600;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s;\n border: none;\n }\n\n .ps-btn-download {\n background: var(--ps-primary);\n color: #111211;\n }\n\n .ps-btn-download:hover {\n opacity: 0.9;\n }\n\n .ps-btn-retry {\n background: rgba(255, 255, 255, 0.1);\n color: var(--ps-text);\n border: 1px solid var(--ps-border) !important;\n }\n\n .ps-btn-retry:hover {\n background: rgba(255, 255, 255, 0.15);\n }\n\n /* ── Error State ────────────────────────── */\n .ps-error {\n text-align: center;\n padding: 24px;\n }\n\n .ps-error-icon {\n width: 48px;\n height: 48px;\n margin: 0 auto 12px;\n stroke: var(--ps-error);\n fill: none;\n stroke-width: 1.5;\n }\n\n .ps-error-text {\n font-size: 14px;\n color: var(--ps-error);\n margin-bottom: 16px;\n }\n\n /* ── Powered By ─────────────────────────── */\n .ps-powered {\n text-align: center;\n padding: 12px 24px 16px;\n font-size: 11px;\n color: var(--ps-text-secondary);\n }\n\n .ps-powered a {\n color: var(--ps-primary);\n text-decoration: none;\n }\n\n .ps-powered a:hover {\n text-decoration: underline;\n }\n ",this.shadow.appendChild(e);const t=this.createButton();if(this.shadow.appendChild(t),"idle"!==this.state){const e=this.createModal();this.shadow.appendChild(e),requestAnimationFrame(()=>e.classList.add("ps-open"))}this.applyButtonStyles(),this.applyModalStyles()}createButton(){const e=document.createElement("button");return e.className="ps-button",e.innerHTML=`${p.camera}<span>${this.buttonText}</span>`,e.addEventListener("click",()=>this.open()),e}createModal(){const e=document.createElement("div");e.className="ps-overlay",e.addEventListener("click",t=>{t.target===e&&this.close()});const t=document.createElement("div");t.className="ps-modal";const n=document.createElement("div");n.className="ps-header",n.innerHTML='\n <span class="ps-header-title">Virtual Try-On</span>\n ';const s=document.createElement("button");s.className="ps-close",s.innerHTML=p.x,s.addEventListener("click",()=>this.close()),n.appendChild(s),t.appendChild(n);const r=document.createElement("div");switch(r.className="ps-body",this.state){case"upload":r.appendChild(this.createUploadView());break;case"processing":r.appendChild(this.createProcessingView());break;case"result":r.appendChild(this.createResultView());break;case"error":r.appendChild(this.createErrorView())}if(t.appendChild(r),this.showPoweredBy){const e=document.createElement("div");e.className="ps-powered",e.innerHTML='Powered by <a href="https://primestyleai.com" target="_blank" rel="noopener">PrimeStyle AI</a>',t.appendChild(e)}return e.appendChild(t),e}createUploadView(){const e=document.createDocumentFragment();if(this.selectedFile&&this.previewUrl){const t=document.createElement("div");t.className="ps-preview";const n=document.createElement("img");n.src=this.previewUrl,n.alt="Your photo",t.appendChild(n);const s=document.createElement("button");s.className="ps-preview-remove",s.textContent="×",s.addEventListener("click",()=>{this.resetUpload(),this.render()}),t.appendChild(s),e.appendChild(t);const r=document.createElement("button");r.className="ps-submit",r.textContent="Try It On",r.addEventListener("click",()=>this.handleSubmit()),e.appendChild(r)}else{const t=document.createElement("div");t.className="ps-upload-zone";const n=document.createElement("input");n.type="file",n.accept="image/jpeg,image/png,image/webp",n.addEventListener("change",e=>{const t=e.target.files?.[0];t&&this.handleFileSelect(t)}),t.innerHTML=`\n <svg class="ps-upload-icon" viewBox="0 0 24 24">${p.upload.replace(/<\/?svg[^>]*>/g,"")}</svg>\n <p class="ps-upload-text">Drop your photo here or click to upload</p>\n <p class="ps-upload-hint">JPEG, PNG or WebP (max 10MB)</p>\n `,t.appendChild(n),t.addEventListener("click",()=>n.click()),t.addEventListener("dragover",e=>{e.preventDefault(),t.classList.add("ps-drag-over")}),t.addEventListener("dragleave",()=>{t.classList.remove("ps-drag-over")}),t.addEventListener("drop",e=>{e.preventDefault(),t.classList.remove("ps-drag-over");const n=e.dataTransfer?.files?.[0];n&&this.handleFileSelect(n)}),e.appendChild(t)}return e}createProcessingView(){const e=document.createElement("div");return e.className="ps-processing",e.innerHTML='\n <div class="ps-spinner"></div>\n <p class="ps-processing-text">Generating your try-on...</p>\n <p class="ps-processing-sub">This usually takes 15-20 seconds</p>\n ',e}createResultView(){const e=document.createElement("div");if(e.className="ps-result",this.resultImageUrl){const t=document.createElement("img");t.src=this.resultImageUrl,t.alt="Try-on result",e.appendChild(t)}const t=document.createElement("div");t.className="ps-result-actions";const n=document.createElement("button");n.className="ps-btn-download",n.textContent="Download",n.addEventListener("click",()=>this.handleDownload()),t.appendChild(n);const s=document.createElement("button");return s.className="ps-btn-retry",s.textContent="Try Another",s.addEventListener("click",()=>{this.resetUpload(),this.state="upload",this.render()}),t.appendChild(s),e.appendChild(t),e}createErrorView(){const e=document.createElement("div");e.className="ps-error",e.innerHTML=`\n <svg class="ps-error-icon" viewBox="0 0 24 24">${p.alert.replace(/<\/?svg[^>]*>/g,"")}</svg>\n <p class="ps-error-text">${this.errorMessage||"Something went wrong"}</p>\n `;const t=document.createElement("button");return t.className="ps-submit",t.textContent="Try Again",t.addEventListener("click",()=>{this.state="upload",this.errorMessage=null,this.render()}),e.appendChild(t),e}handleFileSelect(e){return l(e)?e.size>10485760?(this.errorMessage="Image must be under 10MB.",this.state="error",void this.render()):(this.selectedFile=e,this.previewUrl=URL.createObjectURL(e),this.emit("ps:upload",{file:e}),void this.render()):(this.errorMessage="Please upload a JPEG, PNG, or WebP image.",this.state="error",void this.render())}async handleSubmit(){if(!this.selectedFile||!this.apiClient||!this.sseClient)return this.errorMessage="SDK not configured. Please provide an API key.",this.state="error",void this.render();if(!this.productImageUrl)return this.errorMessage="No product image found. Please set the product-image attribute.",this.state="error",void this.render();this.state="processing",this.render();try{const e=await a(this.selectedFile),t=await this.apiClient.submitTryOn(e,this.productImageUrl);this.currentJobId=t.jobId,this.emit("ps:processing",{jobId:t.jobId}),this.sseUnsubscribe=this.sseClient.onJob(t.jobId,e=>this.handleVtoUpdate(e)),this.startPolling(t.jobId)}catch(e){const t=e instanceof Error?e.message:"Failed to start try-on";this.errorMessage=t,this.state="error",this.emit("ps:error",{message:t,code:e?.code}),this.render()}}handleVtoUpdate(e){"completed"===e.status&&e.imageUrl?("result"!==this.state||this.resultImageUrl?.startsWith("data:")&&!e.imageUrl.startsWith("data:"))&&(this.resultImageUrl=e.imageUrl,this.state="result",this.emit("ps:complete",{jobId:e.galleryId,imageUrl:e.imageUrl}),this.render()):"failed"===e.status&&(this.errorMessage=e.error||"Try-on generation failed",this.state="error",this.emit("ps:error",{message:this.errorMessage}),this.render())}startPolling(e){let t=0;const n=setInterval(async()=>{if(t++,t>60||"result"===this.state||"idle"===this.state)clearInterval(n);else if(this.apiClient)try{const t=await this.apiClient.getStatus(e);"completed"===t.status&&t.imageUrl?("processing"===this.state&&this.handleVtoUpdate({galleryId:e,status:"completed",imageUrl:t.imageUrl,error:null,timestamp:Date.now()}),clearInterval(n)):"failed"===t.status&&("processing"===this.state&&this.handleVtoUpdate({galleryId:e,status:"failed",imageUrl:null,error:t.message,timestamp:Date.now()}),clearInterval(n))}catch{}else clearInterval(n)},2e3)}handleDownload(){if(!this.resultImageUrl)return;const e=document.createElement("a");e.href=this.resultImageUrl,e.download=`primestyle-tryon-${Date.now()}.png`,e.target="_blank",this.resultImageUrl.startsWith("data:")?e.click():fetch(this.resultImageUrl).then(e=>e.blob()).then(t=>{const n=URL.createObjectURL(t);e.href=n,e.click(),setTimeout(()=>URL.revokeObjectURL(n),100)}).catch(()=>{window.open(this.resultImageUrl,"_blank")})}resetUpload(){this.previewUrl&&URL.revokeObjectURL(this.previewUrl),this.selectedFile=null,this.previewUrl=null,this.resultImageUrl=null,this.errorMessage=null,this.currentJobId=null,this.sseUnsubscribe&&(this.sseUnsubscribe(),this.sseUnsubscribe=null)}applyButtonStyles(){if(!this.shadow.querySelector(".ps-button"))return;const e={backgroundColor:"--ps-btn-bg",textColor:"--ps-btn-color",borderRadius:"--ps-btn-radius",fontSize:"--ps-btn-font-size",fontFamily:"--ps-btn-font",fontWeight:"--ps-btn-font-weight",padding:"--ps-btn-padding",border:"--ps-btn-border",width:"--ps-btn-width",height:"--ps-btn-height",hoverBackgroundColor:"--ps-btn-hover-bg",hoverTextColor:"--ps-btn-hover-color",iconSize:"--ps-btn-icon-size",iconColor:"--ps-btn-icon-color",boxShadow:"--ps-btn-shadow"};for(const[t,n]of Object.entries(e)){const e=this.buttonStyles[t];e&&this.style.setProperty(n,e)}}applyModalStyles(){const e={overlayColor:"--ps-modal-overlay",backgroundColor:"--ps-modal-bg",textColor:"--ps-modal-color",borderRadius:"--ps-modal-radius",width:"--ps-modal-width",maxWidth:"--ps-modal-max-width",fontFamily:"--ps-modal-font",headerBackgroundColor:"--ps-modal-header-bg",headerTextColor:"--ps-modal-header-color",closeButtonColor:"--ps-modal-close-color",uploadBorderColor:"--ps-upload-border",uploadBackgroundColor:"--ps-upload-bg",uploadTextColor:"--ps-upload-color",uploadIconColor:"--ps-upload-icon-color",primaryButtonBackgroundColor:"--ps-modal-primary-bg",primaryButtonTextColor:"--ps-modal-primary-color",primaryButtonBorderRadius:"--ps-modal-primary-radius",loaderColor:"--ps-loader",resultBorderRadius:"--ps-result-radius"};for(const[t,n]of Object.entries(e)){const e=this.modalStyles[t];e&&this.style.setProperty(n,e)}}}"undefined"==typeof window||customElements.get("primestyle-tryon")||customElements.define("primestyle-tryon",d),e.ApiClient=t,e.PrimeStyleError=n,e.PrimeStyleTryon=d,e.SseClient=s,e.compressImage=a,e.detectProductImage=r,e.isValidImageFile=l,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});