@primestyleai/tryon 2.0.6 → 2.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/react/index.d.ts +25 -1
- package/dist/react/index.js +210 -14
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
package/dist/react/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CSSProperties } from "react";
|
|
2
|
-
import type { ButtonStyles, ModalStyles, PrimeStyleClassNames } from "../types";
|
|
2
|
+
import type { ButtonStyles, ModalStyles, PrimeStyleClassNames, SizeGuideData } from "../types";
|
|
3
3
|
export interface PrimeStyleTryonProps {
|
|
4
4
|
productImage: string;
|
|
5
5
|
productTitle?: string;
|
|
@@ -23,5 +23,29 @@ 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
50
|
}
|
|
27
51
|
export declare function PrimeStyleTryon(props: PrimeStyleTryonProps): import("react/jsx-runtime").JSX.Element | null;
|
package/dist/react/index.js
CHANGED
|
@@ -21,6 +21,150 @@ 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
|
+
}
|
|
24
168
|
const SIZING_COUNTRIES = [
|
|
25
169
|
{ code: "US", label: "United States" },
|
|
26
170
|
{ code: "UK", label: "United Kingdom" },
|
|
@@ -178,7 +322,15 @@ function PrimeStyleTryonInner({
|
|
|
178
322
|
onUpload,
|
|
179
323
|
onProcessing,
|
|
180
324
|
onComplete,
|
|
181
|
-
onError
|
|
325
|
+
onError,
|
|
326
|
+
sizeGuide: sizeGuideProp,
|
|
327
|
+
productDescription,
|
|
328
|
+
productVendor,
|
|
329
|
+
productType,
|
|
330
|
+
productTags,
|
|
331
|
+
productVariants,
|
|
332
|
+
productOptions,
|
|
333
|
+
scanPageForSizeGuide = false
|
|
182
334
|
}) {
|
|
183
335
|
const [view, setView] = useState("idle");
|
|
184
336
|
const [selectedFile, setSelectedFile] = useState(null);
|
|
@@ -283,18 +435,55 @@ function PrimeStyleTryonInner({
|
|
|
283
435
|
useEffect(() => {
|
|
284
436
|
if (view !== "sizing-choice" || sizeGuideFetchedRef.current || !apiRef.current) return;
|
|
285
437
|
sizeGuideFetchedRef.current = true;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
438
|
+
if (sizeGuideProp) {
|
|
439
|
+
setSizeGuide(sizeGuideProp);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
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
|
+
if (afterClick.found && afterClick.headers && afterClick.rows) {
|
|
454
|
+
setSizeGuide({ found: true, headers: afterClick.headers, rows: afterClick.rows });
|
|
455
|
+
setSizeGuideFetching(false);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
fetchSizeGuideFromApi();
|
|
459
|
+
}, 800);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
fetchSizeGuideFromApi();
|
|
464
|
+
function fetchSizeGuideFromApi() {
|
|
465
|
+
setSizeGuideFetching(true);
|
|
466
|
+
const baseUrl = getApiUrl(apiUrl);
|
|
467
|
+
const key = getApiKey();
|
|
468
|
+
const productPayload = {
|
|
469
|
+
title: productTitle,
|
|
470
|
+
variants: productVariants || []
|
|
471
|
+
};
|
|
472
|
+
if (productDescription) productPayload.description = productDescription;
|
|
473
|
+
if (productVendor) productPayload.vendor = productVendor;
|
|
474
|
+
if (productType) productPayload.productType = productType;
|
|
475
|
+
if (productTags?.length) productPayload.tags = productTags;
|
|
476
|
+
if (productOptions?.length) productPayload.options = productOptions;
|
|
477
|
+
fetch(`${baseUrl}/api/v1/sizing/sizeguide`, {
|
|
478
|
+
method: "POST",
|
|
479
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
480
|
+
body: JSON.stringify({ product: productPayload })
|
|
481
|
+
}).then((r) => r.ok ? r.json() : null).then((data) => {
|
|
482
|
+
if (data) setSizeGuide(data);
|
|
483
|
+
else setSizeGuide({ found: false });
|
|
484
|
+
}).catch(() => setSizeGuide({ found: false })).finally(() => setSizeGuideFetching(false));
|
|
485
|
+
}
|
|
486
|
+
}, [view, apiUrl, productTitle, sizeGuideProp, scanPageForSizeGuide]);
|
|
298
487
|
const stepIndex = useMemo(() => {
|
|
299
488
|
switch (view) {
|
|
300
489
|
case "welcome":
|
|
@@ -402,7 +591,14 @@ function PrimeStyleTryonInner({
|
|
|
402
591
|
const payload = {
|
|
403
592
|
method: sizingMethod,
|
|
404
593
|
locale: sizingCountry,
|
|
405
|
-
product: {
|
|
594
|
+
product: {
|
|
595
|
+
title: productTitle,
|
|
596
|
+
description: productDescription || "",
|
|
597
|
+
variants: productVariants || [],
|
|
598
|
+
...productVendor && { vendor: productVendor },
|
|
599
|
+
...productType && { productType },
|
|
600
|
+
...productTags?.length && { tags: productTags }
|
|
601
|
+
}
|
|
406
602
|
};
|
|
407
603
|
if (sizeGuide?.found) payload.sizeGuide = sizeGuide;
|
|
408
604
|
if (sizingMethod === "exact") {
|
package/dist/types.d.ts
CHANGED
|
@@ -130,6 +130,13 @@ export interface PrimeStyleClassNames {
|
|
|
130
130
|
/** Powered by footer */
|
|
131
131
|
poweredBy?: string;
|
|
132
132
|
}
|
|
133
|
+
/** Pre-computed size guide data — pass directly to skip AI extraction */
|
|
134
|
+
export interface SizeGuideData {
|
|
135
|
+
found: true;
|
|
136
|
+
title?: string;
|
|
137
|
+
headers: string[];
|
|
138
|
+
rows: string[][];
|
|
139
|
+
}
|
|
133
140
|
/** Custom events emitted by the component */
|
|
134
141
|
export interface PrimeStyleEvents {
|
|
135
142
|
"ps:open": CustomEvent<void>;
|