@primestyleai/tryon 2.2.1 → 3.0.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.
@@ -1,5 +1,5 @@
1
1
  import { type CSSProperties } from "react";
2
- import type { ButtonStyles, ModalStyles, PrimeStyleClassNames, SizeGuideData } from "../types";
2
+ import type { ButtonStyles, ModalStyles, PrimeStyleClassNames } from "../types";
3
3
  export interface PrimeStyleTryonProps {
4
4
  productImage: string;
5
5
  productTitle?: string;
@@ -23,29 +23,7 @@ export interface PrimeStyleTryonProps {
23
23
  message: string;
24
24
  code?: string;
25
25
  }) => void;
26
- /** Pre-computed size guide — skips AI extraction if provided */
27
- sizeGuide?: SizeGuideData;
28
- /** Product description HTML for AI size guide extraction */
29
- productDescription?: string;
30
- /** Product vendor/brand for AI extraction */
31
- productVendor?: string;
32
- /** Product type (e.g. "T-Shirt", "Jacket") */
33
- productType?: string;
34
- /** Product tags for AI extraction */
35
- productTags?: string[];
36
- /** Product variants with size options */
37
- productVariants?: Array<{
38
- title: string;
39
- option1?: string | null;
40
- option2?: string | null;
41
- option3?: string | null;
42
- }>;
43
- /** Product options (e.g. [{name: "Size", values: ["S","M","L"]}]) */
44
- productOptions?: Array<{
45
- name: string;
46
- values?: string[];
47
- }>;
48
- /** Scan the page DOM for size guide tables/modals (like the Shopify widget does) */
49
- scanPageForSizeGuide?: boolean;
26
+ /** Size guide data from your backend pass as-is, any format (HTML, JSON, object, string). Our AI will parse it. */
27
+ sizeGuideData?: unknown;
50
28
  }
51
29
  export declare function PrimeStyleTryon(props: PrimeStyleTryonProps): import("react/jsx-runtime").JSX.Element | null;
@@ -21,150 +21,6 @@ function lsSet(key, value) {
21
21
  } catch {
22
22
  }
23
23
  }
24
- function extractTableData(table) {
25
- const headers = [];
26
- const headerRow = table.querySelector("thead tr") || table.querySelector("tr");
27
- if (!headerRow) return null;
28
- for (const cell of headerRow.querySelectorAll("th, td")) {
29
- const txt = (cell.textContent || "").trim();
30
- if (txt) headers.push(txt);
31
- }
32
- if (headers.length < 2) return null;
33
- const rows = [];
34
- for (const tr of table.querySelectorAll("tbody tr, tr")) {
35
- if (tr === headerRow) continue;
36
- const cells = tr.querySelectorAll("td, th");
37
- if (cells.length < 2) continue;
38
- const row = [];
39
- for (const c of cells) row.push((c.textContent || "").trim());
40
- if (row.some((v) => v.length > 0)) rows.push(row);
41
- }
42
- return rows.length > 0 ? { headers, rows } : null;
43
- }
44
- const SIZE_KEYWORDS = /size|chest|bust|waist|hips|shoulder|sleeve|length|inseam|\b(XS|S|M|L|XL|XXL|2XL|3XL)\b/i;
45
- const SG_BUTTON_KEYWORDS = /size.?(guide|chart|table)|sizing|fit.?guide|measurement/i;
46
- function findSizeTables(container) {
47
- const results = [];
48
- for (const table of container.querySelectorAll("table")) {
49
- if (SIZE_KEYWORDS.test(table.textContent || "")) {
50
- const data = extractTableData(table);
51
- if (data) results.push(data);
52
- }
53
- }
54
- return results;
55
- }
56
- function detectUnit(container) {
57
- for (const sel of [".active", ".selected", "[aria-selected=true]", "[class*=active]"]) {
58
- try {
59
- for (const tab of container.querySelectorAll(sel)) {
60
- const t = (tab.textContent || "").trim().toLowerCase();
61
- if (t.length > 20) continue;
62
- if (/\bcm\b|centimeter/i.test(t)) return "cm";
63
- if (/\binch|inches/i.test(t)) return "inches";
64
- }
65
- } catch {
66
- }
67
- }
68
- for (const th of container.querySelectorAll("th")) {
69
- const h = (th.textContent || "").toLowerCase();
70
- if (/\(cm\)/.test(h)) return "cm";
71
- if (/\(in|inch/.test(h)) return "inches";
72
- }
73
- return null;
74
- }
75
- function findSizeGuideButton() {
76
- for (const el of document.querySelectorAll("button, a, [role=button], .size-guide, .size-chart, [data-action*=size], [class*=size-guide], [class*=size-chart], [class*=sizeguide], [class*=sizechart]")) {
77
- const text = (el.textContent || "").trim();
78
- const cls = el.className || "";
79
- const ariaLabel = el.getAttribute("aria-label") || "";
80
- if (SG_BUTTON_KEYWORDS.test(text) || SG_BUTTON_KEYWORDS.test(cls) || SG_BUTTON_KEYWORDS.test(ariaLabel)) {
81
- return el;
82
- }
83
- }
84
- return null;
85
- }
86
- function scanDomForSizeGuide() {
87
- const containerSelectors = [
88
- "[class*=kiwi]",
89
- "[class*=esc-size]",
90
- "[class*=size-matters]",
91
- "[class*=avada-size]",
92
- "[class*=size-guide]",
93
- "[class*=sizeguide]",
94
- "[class*=size-chart]",
95
- "[class*=sizechart]",
96
- "[class*=fit-guide]",
97
- "[class*=fitguide]",
98
- "[id*=size-guide]",
99
- "[id*=sizeguide]",
100
- "[id*=size-chart]",
101
- "[id*=sizechart]"
102
- ];
103
- for (const sel of containerSelectors) {
104
- try {
105
- for (const el of document.querySelectorAll(sel)) {
106
- const tables = findSizeTables(el);
107
- if (tables.length > 0) {
108
- return { found: true, ...tables[0], unit: detectUnit(el) };
109
- }
110
- }
111
- } catch {
112
- }
113
- }
114
- const pageTables = findSizeTables(document);
115
- if (pageTables.length > 0) {
116
- return { found: true, ...pageTables[0] };
117
- }
118
- const sizeBtn = findSizeGuideButton();
119
- if (sizeBtn) {
120
- try {
121
- sizeBtn.click();
122
- } catch {
123
- }
124
- return { found: false, buttonClicked: true };
125
- }
126
- return { found: false };
127
- }
128
- function scanAfterClick() {
129
- const images = [];
130
- const overlaySelectors = [
131
- "[class*=modal]",
132
- "[class*=drawer]",
133
- "[class*=popup]",
134
- "[class*=overlay]",
135
- "[role=dialog]",
136
- "[aria-modal=true]",
137
- "[class*=kiwi]",
138
- "[class*=size-guide]",
139
- "[class*=sizeguide]",
140
- "[class*=size-chart]",
141
- "[class*=sizechart]"
142
- ];
143
- for (const sel of overlaySelectors) {
144
- try {
145
- for (const el of document.querySelectorAll(sel)) {
146
- if (el.classList.contains("ps-tryon-overlay")) continue;
147
- const style = window.getComputedStyle(el);
148
- if (style.display === "none" || style.visibility === "hidden") continue;
149
- const tables = findSizeTables(el);
150
- if (tables.length > 0) {
151
- for (const img of el.querySelectorAll("img")) {
152
- let src = img.src || img.getAttribute("data-src") || "";
153
- if (src.startsWith("//")) src = `https:${src}`;
154
- const w = img.naturalWidth || img.width || 0;
155
- const h = img.naturalHeight || img.height || 0;
156
- if (src.startsWith("http") && (w === 0 || w > 100) && (h === 0 || h > 100)) images.push(src);
157
- }
158
- return { found: true, ...tables[0], unit: detectUnit(el), images };
159
- }
160
- }
161
- } catch {
162
- }
163
- }
164
- const pageTables = findSizeTables(document);
165
- if (pageTables.length > 0) return { found: true, ...pageTables[0], images };
166
- return { found: false, images };
167
- }
168
24
  const SIZING_COUNTRIES = [
169
25
  { code: "US", label: "United States" },
170
26
  { code: "UK", label: "United Kingdom" },
@@ -323,14 +179,7 @@ function PrimeStyleTryonInner({
323
179
  onProcessing,
324
180
  onComplete,
325
181
  onError,
326
- sizeGuide: sizeGuideProp,
327
- productDescription,
328
- productVendor,
329
- productType,
330
- productTags,
331
- productVariants,
332
- productOptions,
333
- scanPageForSizeGuide = false
182
+ sizeGuideData
334
183
  }) {
335
184
  const [view, setView] = useState("idle");
336
185
  const [selectedFile, setSelectedFile] = useState(null);
@@ -435,64 +284,22 @@ function PrimeStyleTryonInner({
435
284
  useEffect(() => {
436
285
  if (view !== "sizing-choice" || sizeGuideFetchedRef.current || !apiRef.current) return;
437
286
  sizeGuideFetchedRef.current = true;
438
- if (sizeGuideProp) {
439
- setSizeGuide(sizeGuideProp);
287
+ if (!sizeGuideData) {
288
+ setSizeGuide({ found: false });
440
289
  return;
441
290
  }
442
- if (scanPageForSizeGuide) {
443
- setSizeGuideFetching(true);
444
- const domResult = scanDomForSizeGuide();
445
- if (domResult.found && domResult.headers && domResult.rows) {
446
- setSizeGuide({ found: true, headers: domResult.headers, rows: domResult.rows });
447
- setSizeGuideFetching(false);
448
- return;
449
- }
450
- if (domResult.buttonClicked) {
451
- setTimeout(() => {
452
- const afterClick = scanAfterClick();
453
- try {
454
- document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
455
- } catch {
456
- }
457
- try {
458
- const closeBtn = document.querySelector("[role=dialog] button[aria-label*=close], [class*=modal] button[class*=close], [aria-modal=true] button");
459
- if (closeBtn) closeBtn.click();
460
- } catch {
461
- }
462
- if (afterClick.found && afterClick.headers && afterClick.rows) {
463
- setSizeGuide({ found: true, headers: afterClick.headers, rows: afterClick.rows });
464
- setSizeGuideFetching(false);
465
- return;
466
- }
467
- fetchSizeGuideFromApi();
468
- }, 800);
469
- return;
470
- }
471
- }
472
- fetchSizeGuideFromApi();
473
- function fetchSizeGuideFromApi() {
474
- setSizeGuideFetching(true);
475
- const baseUrl = getApiUrl(apiUrl);
476
- const key = getApiKey();
477
- const productPayload = {
478
- title: productTitle,
479
- variants: productVariants || []
480
- };
481
- if (productDescription) productPayload.description = productDescription;
482
- if (productVendor) productPayload.vendor = productVendor;
483
- if (productType) productPayload.productType = productType;
484
- if (productTags?.length) productPayload.tags = productTags;
485
- if (productOptions?.length) productPayload.options = productOptions;
486
- fetch(`${baseUrl}/api/v1/sizing/sizeguide`, {
487
- method: "POST",
488
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
489
- body: JSON.stringify({ product: productPayload })
490
- }).then((r) => r.ok ? r.json() : null).then((data) => {
491
- if (data) setSizeGuide(data);
492
- else setSizeGuide({ found: false });
493
- }).catch(() => setSizeGuide({ found: false })).finally(() => setSizeGuideFetching(false));
494
- }
495
- }, [view, apiUrl, productTitle, sizeGuideProp, scanPageForSizeGuide]);
291
+ setSizeGuideFetching(true);
292
+ const baseUrl = getApiUrl(apiUrl);
293
+ const key = getApiKey();
294
+ fetch(`${baseUrl}/api/v1/sizing/sizeguide`, {
295
+ method: "POST",
296
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
297
+ body: JSON.stringify({ product: { title: productTitle }, sizeGuideRaw: sizeGuideData })
298
+ }).then((r) => r.ok ? r.json() : null).then((data) => {
299
+ if (data) setSizeGuide(data);
300
+ else setSizeGuide({ found: false });
301
+ }).catch(() => setSizeGuide({ found: false })).finally(() => setSizeGuideFetching(false));
302
+ }, [view, apiUrl, productTitle, sizeGuideData]);
496
303
  const stepIndex = useMemo(() => {
497
304
  switch (view) {
498
305
  case "welcome":
@@ -600,14 +407,7 @@ function PrimeStyleTryonInner({
600
407
  const payload = {
601
408
  method: sizingMethod,
602
409
  locale: sizingCountry,
603
- product: {
604
- title: productTitle,
605
- description: productDescription || "",
606
- variants: productVariants || [],
607
- ...productVendor && { vendor: productVendor },
608
- ...productType && { productType },
609
- ...productTags?.length && { tags: productTags }
610
- }
410
+ product: { title: productTitle, description: "", variants: [] }
611
411
  };
612
412
  if (sizeGuide?.found) payload.sizeGuide = sizeGuide;
613
413
  if (sizingMethod === "exact") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "2.2.1",
3
+ "version": "3.0.0",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",