@primestyleai/tryon 3.15.0 → 3.16.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.
@@ -306,6 +306,7 @@ const en = {
306
306
  "tap to compare": "tap to compare",
307
307
  "Comparing size": "Comparing size",
308
308
  "Fit Analysis": "Fit Analysis",
309
+ "Show more": "Show more",
309
310
  "Done": "Done",
310
311
  // ── Try-on result ───────────────────────────────────
311
312
  "Try-on result": "Try-on result",
@@ -1,5 +1,5 @@
1
- import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage } from "./index-B8Dg-nJ7.js";
2
- import { P, b, T, d, r } from "./index-B8Dg-nJ7.js";
1
+ import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage } from "./index-DvAzTRFF.js";
2
+ import { P, b, T, d, r } from "./index-DvAzTRFF.js";
3
3
  function detectProductImage() {
4
4
  const ogImage = document.querySelector(
5
5
  'meta[property="og:image"]'
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useMemo, useRef, useCallback } from "react";
4
- import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage, P as PrimeStyleError, L as LOCALE_LABELS, b as SUPPORTED_LOCALES } from "../index-B8Dg-nJ7.js";
4
+ import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage, P as PrimeStyleError, L as LOCALE_LABELS, b as SUPPORTED_LOCALES } from "../index-DvAzTRFF.js";
5
5
  const HEADER_ALIASES = {
6
6
  // ── Size label columns (skipped during field derivation) ──
7
7
  size: "__size__",
@@ -406,20 +406,6 @@ const FALLBACK_FIELDS_MALE = [
406
406
  { key: "inseam", label: "Inseam", required: false, unit: "cm", placeholder: "e.g. 81", category: "body" },
407
407
  { key: "footLengthCm", label: "Foot length", required: false, unit: "cm", placeholder: "e.g. 27", category: "shoe" }
408
408
  ];
409
- const SIZING_COUNTRIES = [
410
- { code: "US", label: "United States" },
411
- { code: "UK", label: "United Kingdom" },
412
- { code: "EU", label: "Europe (EU)" },
413
- { code: "FR", label: "France" },
414
- { code: "IT", label: "Italy" },
415
- { code: "DE", label: "Germany" },
416
- { code: "ES", label: "Spain" },
417
- { code: "JP", label: "Japan" },
418
- { code: "CN", label: "China" },
419
- { code: "KR", label: "South Korea" },
420
- { code: "AU", label: "Australia" },
421
- { code: "BR", label: "Brazil" }
422
- ];
423
409
  const STEP_LABELS = ["", "Welcome", "Size", "Your Fit", "Try On"];
424
410
  const TOTAL_STEPS = 4;
425
411
  function detectLocale() {
@@ -840,7 +826,107 @@ function PrimeStyleTryonInner({
840
826
  }
841
827
  return formGender === "female" ? FALLBACK_FIELDS_FEMALE : FALLBACK_FIELDS_MALE;
842
828
  }, [sizeGuide, formGender]);
829
+ const computeSizingLocally = useCallback(() => {
830
+ if (sizingMethod !== "exact" || !sizeGuide?.found || !sizeGuide.headers || !sizeGuide.rows) return null;
831
+ const HEADER_MAP = {
832
+ chest: "chest",
833
+ bust: "bust",
834
+ waist: "waist",
835
+ hips: "hips",
836
+ hip: "hips",
837
+ shoulder: "shoulderWidth",
838
+ shoulders: "shoulderWidth",
839
+ "shoulder width": "shoulderWidth",
840
+ sleeve: "sleeveLength",
841
+ "sleeve length": "sleeveLength",
842
+ inseam: "inseam",
843
+ neck: "neckCircumference",
844
+ foot: "footLengthCm",
845
+ "foot length": "footLengthCm"
846
+ };
847
+ const INTL = /* @__PURE__ */ new Set(["eu", "us", "uk", "it", "fr", "de", "jp", "cn", "kr", "au", "br", "eur"]);
848
+ const userMeas = {};
849
+ for (const f of dynamicFields) {
850
+ if (f.unit === "size" || ["shoeEU", "shoeUS", "shoeUK"].includes(f.key)) continue;
851
+ const raw = formRef.current[f.key];
852
+ if (!raw) continue;
853
+ const v = parseFloat(raw);
854
+ if (isNaN(v)) continue;
855
+ userMeas[f.key] = sizingUnit === "in" ? inToCm(v) : v;
856
+ }
857
+ for (const k of ["chest", "bust", "waist", "hips", "shoulderWidth", "sleeveLength", "inseam", "neckCircumference", "footLengthCm"]) {
858
+ if (userMeas[k] || !formRef.current[k]) continue;
859
+ const v = parseFloat(formRef.current[k]);
860
+ if (!isNaN(v)) userMeas[k] = sizingUnit === "in" ? inToCm(v) : v;
861
+ }
862
+ if (Object.keys(userMeas).length === 0) return null;
863
+ const sizeColIdx = sizeGuide.headers.findIndex((h) => /^size$/i.test(h.trim()));
864
+ const idx = sizeColIdx >= 0 ? sizeColIdx : 0;
865
+ const colMap = [];
866
+ const intlCols = [];
867
+ sizeGuide.headers.forEach((h, i) => {
868
+ if (i === idx) return;
869
+ const lower = h.toLowerCase().trim().replace(/\s*\(.*\)/, "");
870
+ const clean = lower.replace(/\s*size\s*/gi, "").trim();
871
+ if (INTL.has(clean)) {
872
+ intlCols.push({ hi: i, code: clean.toUpperCase() });
873
+ return;
874
+ }
875
+ const fk = HEADER_MAP[clean];
876
+ if (fk && userMeas[fk] !== void 0) colMap.push({ hi: i, formKey: fk, label: h.trim() });
877
+ });
878
+ if (colMap.length === 0) return null;
879
+ const parseRange = (cell) => {
880
+ const nums = cell.replace(/[^\d.\-–]/g, " ").trim().split(/[\s\-–]+/).filter(Boolean).map(Number).filter((n) => !isNaN(n));
881
+ if (nums.length === 0) return null;
882
+ return { min: Math.min(...nums), max: Math.max(...nums) };
883
+ };
884
+ const scores = [];
885
+ for (const row of sizeGuide.rows) {
886
+ const label = row[idx] || "";
887
+ let fitting = 0;
888
+ let total = 0;
889
+ let dist = 0;
890
+ const details = [];
891
+ for (const col of colMap) {
892
+ const range = parseRange(row[col.hi] || "");
893
+ if (!range) continue;
894
+ total++;
895
+ const uv = userMeas[col.formKey];
896
+ const fit = uv >= range.min && uv <= range.max ? "good" : uv < range.min ? "tight" : "loose";
897
+ if (fit === "good") fitting++;
898
+ dist += Math.abs(uv - (range.min + range.max) / 2);
899
+ const dispVal = sizingUnit === "in" ? `${cmToIn(uv)} in` : `${Math.round(uv)} cm`;
900
+ const dispRange = sizingUnit === "in" ? `${cmToIn(range.min)}–${cmToIn(range.max)} in` : `${Math.round(range.min)}–${Math.round(range.max)} cm`;
901
+ details.push({ measurement: col.label, userValue: dispVal, chartRange: dispRange, fit });
902
+ }
903
+ if (total === 0) continue;
904
+ const intl = {};
905
+ for (const ic of intlCols) {
906
+ if (row[ic.hi]) intl[ic.code] = row[ic.hi];
907
+ }
908
+ scores.push({ label, fitting, total, dist, details, intl, row });
909
+ }
910
+ if (scores.length === 0) return null;
911
+ scores.sort((a, b) => b.fitting - a.fitting || a.dist - b.dist);
912
+ const best = scores[0];
913
+ const conf = best.fitting === best.total ? "high" : best.fitting >= best.total * 0.6 ? "medium" : "low";
914
+ return {
915
+ recommendedSize: best.label,
916
+ confidence: conf,
917
+ reasoning: `Based on your measurements, size ${best.label} is the best fit.`,
918
+ internationalSizes: best.intl,
919
+ matchDetails: best.details,
920
+ method: "deterministic"
921
+ };
922
+ }, [sizingMethod, sizeGuide, dynamicFields, sizingUnit]);
843
923
  const submitSizing = useCallback(async () => {
924
+ const localResult = computeSizingLocally();
925
+ if (localResult) {
926
+ setSizingResult(localResult);
927
+ setSizingLoading(false);
928
+ return;
929
+ }
844
930
  if (!apiRef.current) return;
845
931
  const baseUrl = getApiUrl(apiUrl);
846
932
  const key = getApiKey();
@@ -899,7 +985,7 @@ function PrimeStyleTryonInner({
899
985
  } finally {
900
986
  setSizingLoading(false);
901
987
  }
902
- }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields]);
988
+ }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle, dynamicFields, computeSizingLocally]);
903
989
  const handleTryOnSubmit = useCallback(async () => {
904
990
  if (!selectedFile || !apiRef.current || !sseRef.current) {
905
991
  const msg = !apiRef.current ? t("SDK not configured. Please provide an API key.") : t("Something went wrong");
@@ -1339,10 +1425,6 @@ function PrimeStyleTryonInner({
1339
1425
  ")"
1340
1426
  ] }, p.id))
1341
1427
  ] }) }),
1342
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
1343
- /* @__PURE__ */ jsx("label", { children: t("Sizing region") }),
1344
- /* @__PURE__ */ jsx("select", { className: "ps-tryon-country-select", value: sizingCountry, onChange: (e) => setSizingCountry(e.target.value), children: SIZING_COUNTRIES.map((c) => /* @__PURE__ */ jsx("option", { value: c.code, children: t(c.label) }, c.code)) })
1345
- ] }),
1346
1428
  sizingMethod === "exact" && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-unit-tabs", children: [
1347
1429
  /* @__PURE__ */ jsx("button", { className: `ps-tryon-unit-tab${isCm ? " ps-active" : ""}`, onClick: () => {
1348
1430
  setSizingUnit("cm");
@@ -1599,13 +1681,30 @@ function PrimeStyleTryonInner({
1599
1681
  /* @__PURE__ */ jsx("div", { className: `ps-tryon-sr-hero-conf ps-conf-${sizingResult.confidence}`, children: sizingResult.confidence === "high" ? t("High Confidence") : sizingResult.confidence === "medium" ? t("Medium Confidence") : t("Low Confidence") })
1600
1682
  ] })
1601
1683
  ] }),
1602
- Object.keys(activeIntl).length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl", children: [
1603
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Equivalent Sizes") }),
1604
- /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-intl-grid", children: Object.entries(activeIntl).map(([code, val]) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl-item", children: [
1605
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-code", children: code }),
1606
- /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-val", children: val })
1607
- ] }, code)) })
1608
- ] }),
1684
+ Object.keys(activeIntl).length > 0 && (() => {
1685
+ const PRIMARY_CODES = ["US", "UK", "EU", "IT", "FR", "DE"];
1686
+ const primary = PRIMARY_CODES.filter((c) => activeIntl[c]).map((c) => ({ code: c, val: activeIntl[c] }));
1687
+ const rest = Object.entries(activeIntl).filter(([c]) => !PRIMARY_CODES.includes(c)).map(([code, val]) => ({ code, val }));
1688
+ return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl", children: [
1689
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-label", children: t("Equivalent Sizes") }),
1690
+ primary.length > 0 && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-intl-primary", children: primary.map((s) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl-card", children: [
1691
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-card-val", children: s.val }),
1692
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-card-code", children: s.code })
1693
+ ] }, s.code)) }),
1694
+ rest.length > 0 && /* @__PURE__ */ jsxs("details", { className: "ps-tryon-sr-intl-more", children: [
1695
+ /* @__PURE__ */ jsxs("summary", { className: "ps-tryon-sr-intl-more-btn", children: [
1696
+ t("Show more"),
1697
+ " (",
1698
+ rest.length,
1699
+ ")"
1700
+ ] }),
1701
+ /* @__PURE__ */ jsx("div", { className: "ps-tryon-sr-intl-grid", children: rest.map((s) => /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-intl-item", children: [
1702
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-code", children: s.code }),
1703
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-sr-intl-val", children: s.val })
1704
+ ] }, s.code)) })
1705
+ ] })
1706
+ ] });
1707
+ })(),
1609
1708
  chartData && chartData.sizes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-sizes", children: [
1610
1709
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sr-label", children: [
1611
1710
  t("Size Chart"),
@@ -2376,6 +2475,25 @@ const STYLES = `
2376
2475
  .ps-tryon-sr-intl { }
2377
2476
  .ps-tryon-sr-label { font-size: 0.78vw; font-weight: 700; color: #999; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.52vw; }
2378
2477
  .ps-tryon-sr-label-hint { font-weight: 400; text-transform: none; letter-spacing: 0; color: #666; font-style: italic; }
2478
+ .ps-tryon-sr-intl-primary { display: flex; flex-wrap: wrap; gap: 0.42vw; margin-bottom: 0.52vw; }
2479
+ .ps-tryon-sr-intl-card {
2480
+ flex: 1; min-width: 3.5vw; display: flex; flex-direction: column; align-items: center;
2481
+ padding: 0.57vw 0.42vw; border: 1.5px solid #333; border-radius: 0.63vw;
2482
+ background: #1a1b1a; transition: border-color 0.2s;
2483
+ }
2484
+ .ps-tryon-sr-intl-card:hover { border-color: #555; }
2485
+ .ps-tryon-sr-intl-card-val { font-size: 1.04vw; font-weight: 800; color: #fff; line-height: 1.2; }
2486
+ .ps-tryon-sr-intl-card-code { font-size: 0.57vw; font-weight: 700; color: #666; text-transform: uppercase; letter-spacing: 0.08em; margin-top: 0.1vw; }
2487
+
2488
+ .ps-tryon-sr-intl-more { }
2489
+ .ps-tryon-sr-intl-more-btn {
2490
+ font-size: 0.73vw; color: #bb945c; font-weight: 600; cursor: pointer;
2491
+ list-style: none; margin-bottom: 0.42vw; font-family: inherit;
2492
+ }
2493
+ .ps-tryon-sr-intl-more-btn::-webkit-details-marker { display: none; }
2494
+ .ps-tryon-sr-intl-more-btn::before { content: "▸ "; }
2495
+ .ps-tryon-sr-intl-more[open] .ps-tryon-sr-intl-more-btn::before { content: "▾ "; }
2496
+
2379
2497
  .ps-tryon-sr-intl-grid { display: flex; flex-wrap: wrap; gap: 0.42vw; }
2380
2498
  .ps-tryon-sr-intl-item {
2381
2499
  display: flex; align-items: center; border: 1.5px solid #333; border-radius: 0.52vw; overflow: hidden;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",