@primestyleai/tryon 2.0.2 → 2.0.4

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.
Files changed (2) hide show
  1. package/dist/react/index.js +125 -81
  2. package/package.json +1 -1
@@ -182,12 +182,15 @@ function PrimeStyleTryon({
182
182
  const [sizingMethod, setSizingMethod] = useState(null);
183
183
  const [sizingResult, setSizingResult] = useState(null);
184
184
  const [sizeGuide, setSizeGuide] = useState(null);
185
+ const [sizeGuideFetching, setSizeGuideFetching] = useState(false);
186
+ const sizeGuideFetchedRef = useRef(false);
185
187
  const [sizingCountry, setSizingCountry] = useState(detectLocale);
186
188
  const imperial = isImperial(sizingCountry);
187
189
  const [sizingUnit, setSizingUnit] = useState(imperial ? "in" : "cm");
188
190
  const [heightUnit, setHeightUnit] = useState(imperial ? "ft" : "cm");
189
191
  const [weightUnit, setWeightUnit] = useState(imperial ? "lbs" : "kg");
190
- const [formData, setFormData] = useState({});
192
+ const formRef = useRef({});
193
+ const [formGender, setFormGender] = useState("male");
191
194
  const [profiles, setProfiles] = useState(() => lsGet("profiles", []));
192
195
  const [history, setHistory] = useState(() => lsGet("history", []));
193
196
  const [activeProfileId, setActiveProfileId] = useState(null);
@@ -256,6 +259,21 @@ function PrimeStyleTryon({
256
259
  useEffect(() => {
257
260
  lsSet("history", history);
258
261
  }, [history]);
262
+ useEffect(() => {
263
+ if (view !== "sizing-choice" || sizeGuideFetchedRef.current || !apiRef.current) return;
264
+ sizeGuideFetchedRef.current = true;
265
+ setSizeGuideFetching(true);
266
+ const baseUrl = getApiUrl(apiUrl);
267
+ const key = getApiKey();
268
+ fetch(`${baseUrl}/api/v1/sizing/sizeguide`, {
269
+ method: "POST",
270
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
271
+ body: JSON.stringify({ product: { title: productTitle, variants: [] } })
272
+ }).then((r) => r.ok ? r.json() : null).then((data) => {
273
+ if (data) setSizeGuide(data);
274
+ else setSizeGuide({ found: false });
275
+ }).catch(() => setSizeGuide({ found: false })).finally(() => setSizeGuideFetching(false));
276
+ }, [view, apiUrl, productTitle]);
259
277
  const stepIndex = useMemo(() => {
260
278
  switch (view) {
261
279
  case "welcome":
@@ -290,7 +308,10 @@ function PrimeStyleTryon({
290
308
  setSizingResult(null);
291
309
  setSizeGuide(null);
292
310
  setProfileSaved(false);
293
- setFormData({});
311
+ formRef.current = {};
312
+ setFormGender("male");
313
+ sizeGuideFetchedRef.current = false;
314
+ setSizeGuideFetching(false);
294
315
  unsubRef.current?.();
295
316
  unsubRef.current = null;
296
317
  if (pollingRef.current) {
@@ -364,23 +385,23 @@ function PrimeStyleTryon({
364
385
  };
365
386
  if (sizeGuide?.found) payload.sizeGuide = sizeGuide;
366
387
  if (sizingMethod === "exact") {
367
- const m = { gender: formData.gender || "male" };
368
- if (formData.height) m.heightCm = heightUnit === "ft" ? ftInToCm(parseFloat(formData.heightFeet || "0"), parseFloat(formData.heightInches || "0")) : parseFloat(formData.height);
369
- if (formData.weight) m.weightKg = weightUnit === "lbs" ? lbsToKg(parseFloat(formData.weight)) : parseFloat(formData.weight);
388
+ const m = { gender: formRef.current.gender || "male" };
389
+ if (formRef.current.height) m.heightCm = heightUnit === "ft" ? ftInToCm(parseFloat(formRef.current.heightFeet || "0"), parseFloat(formRef.current.heightInches || "0")) : parseFloat(formRef.current.height);
390
+ if (formRef.current.weight) m.weightKg = weightUnit === "lbs" ? lbsToKg(parseFloat(formRef.current.weight)) : parseFloat(formRef.current.weight);
370
391
  const keys = ["chest", "bust", "waist", "hips", "shoulderWidth", "sleeveLength", "inseam", "neckCircumference", "footLengthCm"];
371
392
  for (const k of keys) {
372
- if (formData[k]) m[k] = sizingUnit === "in" ? inToCm(parseFloat(formData[k])) : parseFloat(formData[k]);
393
+ if (formRef.current[k]) m[k] = sizingUnit === "in" ? inToCm(parseFloat(formRef.current[k])) : parseFloat(formRef.current[k]);
373
394
  }
374
- if (formData.shoeEU) m.shoeEU = formData.shoeEU;
375
- if (formData.shoeUS) m.shoeUS = formData.shoeUS;
376
- if (formData.shoeUK) m.shoeUK = formData.shoeUK;
377
- if (formData.fitPreference) m.fitPreference = formData.fitPreference;
395
+ if (formRef.current.shoeEU) m.shoeEU = formRef.current.shoeEU;
396
+ if (formRef.current.shoeUS) m.shoeUS = formRef.current.shoeUS;
397
+ if (formRef.current.shoeUK) m.shoeUK = formRef.current.shoeUK;
398
+ if (formRef.current.fitPreference) m.fitPreference = formRef.current.fitPreference;
378
399
  payload.measurements = m;
379
400
  } else {
380
401
  payload.quickEstimate = {
381
- heightCm: heightUnit === "ft" ? ftInToCm(parseFloat(formData.heightFeet || "0"), parseFloat(formData.heightInches || "0")) : parseFloat(formData.height || "0"),
382
- weightKg: weightUnit === "lbs" ? lbsToKg(parseFloat(formData.weight || "0")) : parseFloat(formData.weight || "0"),
383
- gender: formData.gender || "male"
402
+ heightCm: heightUnit === "ft" ? ftInToCm(parseFloat(formRef.current.heightFeet || "0"), parseFloat(formRef.current.heightInches || "0")) : parseFloat(formRef.current.height || "0"),
403
+ weightKg: weightUnit === "lbs" ? lbsToKg(parseFloat(formRef.current.weight || "0")) : parseFloat(formRef.current.weight || "0"),
404
+ gender: formRef.current.gender || "male"
384
405
  };
385
406
  }
386
407
  try {
@@ -395,7 +416,7 @@ function PrimeStyleTryon({
395
416
  }
396
417
  } catch {
397
418
  }
398
- }, [apiUrl, sizingMethod, sizingCountry, formData, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle]);
419
+ }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle]);
399
420
  const handleSubmit = useCallback(async () => {
400
421
  if (!selectedFile || !apiRef.current || !sseRef.current) {
401
422
  const msg = !apiRef.current ? "Missing NEXT_PUBLIC_PRIMESTYLE_API_KEY" : "No file selected";
@@ -492,29 +513,30 @@ function PrimeStyleTryon({
492
513
  if (p.shoeUS) fd.shoeUS = p.shoeUS;
493
514
  if (p.shoeUK) fd.shoeUK = p.shoeUK;
494
515
  if (p.fitPreference) fd.fitPreference = p.fitPreference;
495
- setFormData(fd);
516
+ formRef.current = fd;
517
+ setFormGender(fd.gender || "male");
496
518
  }, [profiles]);
497
519
  const saveProfile = useCallback((name) => {
498
520
  const id = activeProfileId || `p_${Date.now()}`;
499
521
  const p = {
500
522
  id,
501
523
  name,
502
- gender: formData.gender || "male",
503
- heightCm: formData.height ? parseFloat(formData.height) : void 0,
504
- weightKg: formData.weight ? parseFloat(formData.weight) : void 0,
505
- chest: formData.chest ? parseFloat(formData.chest) : void 0,
506
- bust: formData.bust ? parseFloat(formData.bust) : void 0,
507
- waist: formData.waist ? parseFloat(formData.waist) : void 0,
508
- hips: formData.hips ? parseFloat(formData.hips) : void 0,
509
- shoulderWidth: formData.shoulderWidth ? parseFloat(formData.shoulderWidth) : void 0,
510
- sleeveLength: formData.sleeveLength ? parseFloat(formData.sleeveLength) : void 0,
511
- inseam: formData.inseam ? parseFloat(formData.inseam) : void 0,
512
- neckCircumference: formData.neckCircumference ? parseFloat(formData.neckCircumference) : void 0,
513
- footLengthCm: formData.footLengthCm ? parseFloat(formData.footLengthCm) : void 0,
514
- shoeEU: formData.shoeEU,
515
- shoeUS: formData.shoeUS,
516
- shoeUK: formData.shoeUK,
517
- fitPreference: formData.fitPreference,
524
+ gender: formRef.current.gender || "male",
525
+ heightCm: formRef.current.height ? parseFloat(formRef.current.height) : void 0,
526
+ weightKg: formRef.current.weight ? parseFloat(formRef.current.weight) : void 0,
527
+ chest: formRef.current.chest ? parseFloat(formRef.current.chest) : void 0,
528
+ bust: formRef.current.bust ? parseFloat(formRef.current.bust) : void 0,
529
+ waist: formRef.current.waist ? parseFloat(formRef.current.waist) : void 0,
530
+ hips: formRef.current.hips ? parseFloat(formRef.current.hips) : void 0,
531
+ shoulderWidth: formRef.current.shoulderWidth ? parseFloat(formRef.current.shoulderWidth) : void 0,
532
+ sleeveLength: formRef.current.sleeveLength ? parseFloat(formRef.current.sleeveLength) : void 0,
533
+ inseam: formRef.current.inseam ? parseFloat(formRef.current.inseam) : void 0,
534
+ neckCircumference: formRef.current.neckCircumference ? parseFloat(formRef.current.neckCircumference) : void 0,
535
+ footLengthCm: formRef.current.footLengthCm ? parseFloat(formRef.current.footLengthCm) : void 0,
536
+ shoeEU: formRef.current.shoeEU,
537
+ shoeUS: formRef.current.shoeUS,
538
+ shoeUK: formRef.current.shoeUK,
539
+ fitPreference: formRef.current.fitPreference,
518
540
  createdAt: Date.now()
519
541
  };
520
542
  setProfiles((prev) => {
@@ -528,7 +550,7 @@ function PrimeStyleTryon({
528
550
  });
529
551
  setActiveProfileId(id);
530
552
  setProfileSaved(true);
531
- }, [activeProfileId, formData]);
553
+ }, [activeProfileId]);
532
554
  const saveHistoryEntry = useCallback(() => {
533
555
  const entry = {
534
556
  id: `h_${Date.now()}`,
@@ -554,7 +576,7 @@ function PrimeStyleTryon({
554
576
  }
555
577
  }, [view]);
556
578
  const updateField = useCallback((key, val) => {
557
- setFormData((prev) => ({ ...prev, [key]: val }));
579
+ formRef.current[key] = val;
558
580
  }, []);
559
581
  const shoeField = useMemo(() => {
560
582
  const map = {
@@ -619,8 +641,8 @@ function PrimeStyleTryon({
619
641
  {
620
642
  type,
621
643
  placeholder,
622
- value: formData[fieldKey] || "",
623
- onChange: (e) => updateField(fieldKey, e.target.value)
644
+ defaultValue: formRef.current[fieldKey] || "",
645
+ onInput: (e) => updateField(fieldKey, e.target.value)
624
646
  }
625
647
  ),
626
648
  unit && /* @__PURE__ */ jsx("span", { className: "ps-tryon-input-unit", children: unit })
@@ -716,10 +738,15 @@ function PrimeStyleTryon({
716
738
  ] });
717
739
  }
718
740
  function SizingChoiceView() {
741
+ const sgAvailable = sizeGuide?.found === true;
742
+ const sgDisabled = !sizeGuideFetching && !sgAvailable;
743
+ const disabledClass = sgDisabled ? " ps-tryon-choice-disabled" : sizeGuideFetching ? " ps-tryon-choice-loading" : "";
719
744
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sizing-choice", children: [
720
745
  /* @__PURE__ */ jsx("h3", { className: "ps-tryon-section-title", children: "How would you like to find your size?" }),
746
+ sgDisabled && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sg-notice", children: "Size guide is not available for this product" }),
747
+ sizeGuideFetching && /* @__PURE__ */ jsx("div", { className: "ps-tryon-sg-notice ps-tryon-sg-loading", children: "Checking size guide availability..." }),
721
748
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-choice-cards", children: [
722
- /* @__PURE__ */ jsxs("button", { className: "ps-tryon-choice-card", onClick: () => {
749
+ /* @__PURE__ */ jsxs("button", { className: `ps-tryon-choice-card${disabledClass}`, disabled: sgDisabled || sizeGuideFetching, onClick: () => {
723
750
  setSizingMethod("exact");
724
751
  setView("sizing-form");
725
752
  }, children: [
@@ -730,7 +757,7 @@ function PrimeStyleTryon({
730
757
  ] }),
731
758
  /* @__PURE__ */ jsx("span", { className: "ps-tryon-choice-badge", children: "Best accuracy" })
732
759
  ] }),
733
- /* @__PURE__ */ jsxs("button", { className: "ps-tryon-choice-card", onClick: () => {
760
+ /* @__PURE__ */ jsxs("button", { className: `ps-tryon-choice-card${disabledClass}`, disabled: sgDisabled || sizeGuideFetching, onClick: () => {
734
761
  setSizingMethod("quick");
735
762
  setView("sizing-form");
736
763
  }, children: [
@@ -754,58 +781,62 @@ function PrimeStyleTryon({
754
781
  ] });
755
782
  }
756
783
  function SizingFormView() {
757
- const isFemale = formData.gender === "female";
784
+ const isFemale = formGender === "female";
785
+ const isCm = sizingUnit === "cm";
758
786
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-sizing-form", children: [
759
787
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
760
- /* @__PURE__ */ jsx("label", { children: "Sizing region" }),
761
- /* @__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: c.label }, c.code)) })
762
- ] }),
763
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
764
- /* @__PURE__ */ jsx("label", { children: "Gender" }),
788
+ /* @__PURE__ */ jsx("label", { children: "I'm shopping for" }),
765
789
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-unit-toggle", children: [
766
- /* @__PURE__ */ jsx("button", { className: `ps-tryon-unit-btn${formData.gender !== "female" ? " ps-active" : ""}`, onClick: () => updateField("gender", "male"), children: "Men's" }),
767
- /* @__PURE__ */ jsx("button", { className: `ps-tryon-unit-btn${formData.gender === "female" ? " ps-active" : ""}`, onClick: () => updateField("gender", "female"), children: "Women's" })
790
+ /* @__PURE__ */ jsx("button", { className: `ps-tryon-unit-btn${!isFemale ? " ps-active" : ""}`, onClick: () => {
791
+ updateField("gender", "male");
792
+ setFormGender("male");
793
+ }, children: "Men's" }),
794
+ /* @__PURE__ */ jsx("button", { className: `ps-tryon-unit-btn${isFemale ? " ps-active" : ""}`, onClick: () => {
795
+ updateField("gender", "female");
796
+ setFormGender("female");
797
+ }, children: "Women's" })
768
798
  ] })
769
799
  ] }),
800
+ /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
801
+ /* @__PURE__ */ jsx("label", { children: "Sizing region" }),
802
+ /* @__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: c.label }, c.code)) })
803
+ ] }),
804
+ sizingMethod === "exact" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "cm", value: "cm" }, { label: "in", value: "in" }], value: sizingUnit, onChange: setSizingUnit }) }),
770
805
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
771
806
  /* @__PURE__ */ jsx("label", { children: "Height" }),
772
807
  heightUnit === "ft" ? /* @__PURE__ */ jsxs("div", { className: "ps-tryon-height-ft", children: [
773
- /* @__PURE__ */ jsx("input", { type: "number", placeholder: "5", value: formData.heightFeet || "", onChange: (e) => updateField("heightFeet", e.target.value) }),
808
+ /* @__PURE__ */ jsx("input", { type: "number", placeholder: "5", defaultValue: formRef.current.heightFeet || "", onInput: (e) => updateField("heightFeet", e.target.value) }),
774
809
  /* @__PURE__ */ jsx("span", { children: "ft" }),
775
- /* @__PURE__ */ jsx("input", { type: "number", placeholder: "4", value: formData.heightInches || "", onChange: (e) => updateField("heightInches", e.target.value) }),
810
+ /* @__PURE__ */ jsx("input", { type: "number", placeholder: "4", defaultValue: formRef.current.heightInches || "", onInput: (e) => updateField("heightInches", e.target.value) }),
776
811
  /* @__PURE__ */ jsx("span", { children: "in" })
777
- ] }) : /* @__PURE__ */ jsx("input", { type: "number", placeholder: "e.g. 175", value: formData.height || "", onChange: (e) => updateField("height", e.target.value) }),
812
+ ] }) : /* @__PURE__ */ jsx("input", { type: "number", placeholder: "e.g. 175", defaultValue: formRef.current.height || "", onInput: (e) => updateField("height", e.target.value) }),
778
813
  /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "cm", value: "cm" }, { label: "ft", value: "ft" }], value: heightUnit, onChange: setHeightUnit })
779
814
  ] }),
780
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
781
- /* @__PURE__ */ jsx("label", { children: "Weight" }),
782
- /* @__PURE__ */ jsx("input", { type: "number", placeholder: weightUnit === "lbs" ? "e.g. 170" : "e.g. 75", value: formData.weight || "", onChange: (e) => updateField("weight", e.target.value) }),
783
- /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "kg", value: "kg" }, { label: "lbs", value: "lbs" }], value: weightUnit, onChange: setWeightUnit })
784
- ] }),
785
- sizingMethod === "exact" && /* @__PURE__ */ jsxs(Fragment, { children: [
786
- /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
787
- /* @__PURE__ */ jsx("label", { children: "Measurements" }),
788
- /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "cm", value: "cm" }, { label: "in", value: "in" }], value: sizingUnit, onChange: setSizingUnit })
789
- ] }),
815
+ sizingMethod === "exact" ? /* @__PURE__ */ jsxs(Fragment, { children: [
790
816
  isFemale ? /* @__PURE__ */ jsxs(Fragment, { children: [
791
- /* @__PURE__ */ jsx(InputRow, { label: "Bust", fieldKey: "bust", placeholder: "e.g. 90", type: "number", unit: sizingUnit }),
792
- /* @__PURE__ */ jsx(InputRow, { label: "Waist", fieldKey: "waist", placeholder: "e.g. 70", type: "number", unit: sizingUnit }),
793
- /* @__PURE__ */ jsx(InputRow, { label: "Hips", fieldKey: "hips", placeholder: "e.g. 95", type: "number", unit: sizingUnit })
817
+ /* @__PURE__ */ jsx(InputRow, { label: "Bust *", fieldKey: "bust", placeholder: isCm ? "e.g. 88" : "e.g. 35", type: "number", unit: sizingUnit }),
818
+ /* @__PURE__ */ jsx(InputRow, { label: "Waist *", fieldKey: "waist", placeholder: isCm ? "e.g. 70" : "e.g. 28", type: "number", unit: sizingUnit }),
819
+ /* @__PURE__ */ jsx(InputRow, { label: "Hips *", fieldKey: "hips", placeholder: isCm ? "e.g. 96" : "e.g. 38", type: "number", unit: sizingUnit }),
820
+ /* @__PURE__ */ jsx(InputRow, { label: "Shoulders", fieldKey: "shoulderWidth", placeholder: isCm ? "e.g. 39" : "e.g. 15", type: "number", unit: sizingUnit }),
821
+ /* @__PURE__ */ jsx(InputRow, { label: "Inseam", fieldKey: "inseam", placeholder: isCm ? "e.g. 76" : "e.g. 30", type: "number", unit: sizingUnit })
794
822
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
795
- /* @__PURE__ */ jsx(InputRow, { label: "Chest", fieldKey: "chest", placeholder: "e.g. 100", type: "number", unit: sizingUnit }),
796
- /* @__PURE__ */ jsx(InputRow, { label: "Waist", fieldKey: "waist", placeholder: "e.g. 82", type: "number", unit: sizingUnit }),
797
- /* @__PURE__ */ jsx(InputRow, { label: "Hips", fieldKey: "hips", placeholder: "e.g. 98", type: "number", unit: sizingUnit })
823
+ /* @__PURE__ */ jsx(InputRow, { label: "Chest *", fieldKey: "chest", placeholder: isCm ? "e.g. 104" : "e.g. 41", type: "number", unit: sizingUnit }),
824
+ /* @__PURE__ */ jsx(InputRow, { label: "Waist *", fieldKey: "waist", placeholder: isCm ? "e.g. 84" : "e.g. 33", type: "number", unit: sizingUnit }),
825
+ /* @__PURE__ */ jsx(InputRow, { label: "Shoulders", fieldKey: "shoulderWidth", placeholder: isCm ? "e.g. 46" : "e.g. 18", type: "number", unit: sizingUnit }),
826
+ /* @__PURE__ */ jsx(InputRow, { label: "Sleeve", fieldKey: "sleeveLength", placeholder: isCm ? "e.g. 64" : "e.g. 25", type: "number", unit: sizingUnit }),
827
+ /* @__PURE__ */ jsx(InputRow, { label: "Inseam", fieldKey: "inseam", placeholder: isCm ? "e.g. 81" : "e.g. 32", type: "number", unit: sizingUnit })
798
828
  ] }),
799
- /* @__PURE__ */ jsx(InputRow, { label: "Shoulder Width", fieldKey: "shoulderWidth", placeholder: "e.g. 45", type: "number", unit: sizingUnit }),
800
- /* @__PURE__ */ jsx(InputRow, { label: "Sleeve Length", fieldKey: "sleeveLength", placeholder: "e.g. 65", type: "number", unit: sizingUnit }),
801
- /* @__PURE__ */ jsx(InputRow, { label: "Inseam", fieldKey: "inseam", placeholder: "e.g. 80", type: "number", unit: sizingUnit }),
802
829
  isFemale && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
803
- /* @__PURE__ */ jsx("label", { children: "Fit preference" }),
830
+ /* @__PURE__ */ jsx("label", { children: "Fit type" }),
804
831
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-unit-toggle", children: ["petite", "standard", "tall", "plus"].map((fp) => /* @__PURE__ */ jsx(
805
832
  "button",
806
833
  {
807
- className: `ps-tryon-unit-btn${(formData.fitPreference || "standard") === fp ? " ps-active" : ""}`,
808
- onClick: () => updateField("fitPreference", fp),
834
+ className: `ps-tryon-unit-btn${(formRef.current.fitPreference || "standard") === fp ? " ps-active" : ""}`,
835
+ onClick: (e) => {
836
+ updateField("fitPreference", fp);
837
+ const btns = e.target.parentElement.querySelectorAll(".ps-tryon-unit-btn");
838
+ btns.forEach((b) => b.classList.toggle("ps-active", b.textContent?.toLowerCase() === fp));
839
+ },
809
840
  children: fp.charAt(0).toUpperCase() + fp.slice(1)
810
841
  },
811
842
  fp
@@ -813,16 +844,20 @@ function PrimeStyleTryon({
813
844
  ] }),
814
845
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-shoe-section", children: [
815
846
  /* @__PURE__ */ jsx("div", { className: "ps-tryon-shoe-title", children: "Shoe sizing (optional)" }),
816
- /* @__PURE__ */ jsx(InputRow, { label: "Foot length", fieldKey: "footLengthCm", placeholder: sizingUnit === "in" ? "e.g. 10.5" : "e.g. 27", type: "number", unit: sizingUnit }),
847
+ /* @__PURE__ */ jsx(InputRow, { label: "Foot length", fieldKey: "footLengthCm", placeholder: isCm ? "e.g. 27" : "e.g. 10.5", type: "number", unit: sizingUnit }),
817
848
  /* @__PURE__ */ jsx(InputRow, { label: shoeField.label, fieldKey: shoeField.key, placeholder: shoeField.ph })
818
849
  ] })
819
- ] }),
850
+ ] }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
851
+ /* @__PURE__ */ jsx("label", { children: "Weight" }),
852
+ /* @__PURE__ */ jsx("input", { type: "number", placeholder: weightUnit === "lbs" ? "e.g. 170" : "e.g. 75", defaultValue: formRef.current.weight || "", onInput: (e) => updateField("weight", e.target.value) }),
853
+ /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "kg", value: "kg" }, { label: "lbs", value: "lbs" }], value: weightUnit, onChange: setWeightUnit })
854
+ ] }) }),
820
855
  /* @__PURE__ */ jsx("p", { className: "ps-tryon-disclaimer", children: "Fill in what you know — more measurements = better accuracy." }),
821
856
  /* @__PURE__ */ jsxs("button", { className: "ps-tryon-submit", onClick: handleSubmit, children: [
822
857
  "Get My Size & Try On ",
823
858
  /* @__PURE__ */ jsx(ArrowRightIcon, {})
824
859
  ] })
825
- ] });
860
+ ] }, `form-${formGender}-${sizingUnit}-${heightUnit}-${sizingCountry}`);
826
861
  }
827
862
  function ProcessingView() {
828
863
  return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-processing", children: [
@@ -996,19 +1031,19 @@ function PrimeStyleTryon({
996
1031
  function renderBody() {
997
1032
  switch (view) {
998
1033
  case "welcome":
999
- return /* @__PURE__ */ jsx(WelcomeView, {});
1034
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(WelcomeView, {}) }, "v-welcome");
1000
1035
  case "upload":
1001
- return /* @__PURE__ */ jsx(UploadView, {});
1036
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(UploadView, {}) }, "v-upload");
1002
1037
  case "sizing-choice":
1003
- return /* @__PURE__ */ jsx(SizingChoiceView, {});
1038
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(SizingChoiceView, {}) }, "v-choice");
1004
1039
  case "sizing-form":
1005
- return /* @__PURE__ */ jsx(SizingFormView, {});
1040
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(SizingFormView, {}) }, "v-form");
1006
1041
  case "processing":
1007
- return /* @__PURE__ */ jsx(ProcessingView, {});
1042
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(ProcessingView, {}) }, "v-proc");
1008
1043
  case "result":
1009
- return /* @__PURE__ */ jsx(ResultView, {});
1044
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(ResultView, {}) }, "v-result");
1010
1045
  case "error":
1011
- return /* @__PURE__ */ jsx(ErrorView, {});
1046
+ return /* @__PURE__ */ jsx("div", { className: "ps-tryon-view-enter", children: /* @__PURE__ */ jsx(ErrorView, {}) }, "v-error");
1012
1047
  default:
1013
1048
  return null;
1014
1049
  }
@@ -1125,8 +1160,8 @@ const STYLES = `
1125
1160
 
1126
1161
  /* Body */
1127
1162
  .ps-tryon-body { padding: 24px; min-height: 300px; }
1128
- .ps-tryon-body > * { animation: ps-fade-up 0.35s ease both; }
1129
1163
  @keyframes ps-fade-up { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
1164
+ .ps-tryon-view-enter { animation: ps-fade-up 0.35s ease both; }
1130
1165
 
1131
1166
  /* Welcome */
1132
1167
  .ps-tryon-welcome { text-align: center; }
@@ -1217,6 +1252,15 @@ const STYLES = `
1217
1252
  padding: 3px 10px; border-radius: 20px; flex-shrink: 0;
1218
1253
  background: rgba(187,148,92,0.12); color: #bb945c; font-size: 10px; font-weight: 600;
1219
1254
  }
1255
+ .ps-tryon-choice-disabled { opacity: 0.4; cursor: not-allowed !important; pointer-events: none; }
1256
+ .ps-tryon-choice-disabled:hover { border-color: #333; transform: none; box-shadow: none; }
1257
+ .ps-tryon-choice-disabled::before { display: none; }
1258
+ .ps-tryon-choice-loading { opacity: 0.5; cursor: wait !important; pointer-events: none; }
1259
+ .ps-tryon-sg-notice {
1260
+ font-size: 12px; color: #999; text-align: center; padding: 10px 14px;
1261
+ margin-bottom: 12px; border: 1px solid #333; border-radius: 10px; background: #1a1b1a;
1262
+ }
1263
+ .ps-tryon-sg-loading { border-style: dashed; }
1220
1264
 
1221
1265
  /* Sizing form */
1222
1266
  .ps-tryon-sizing-form { display: flex; flex-direction: column; gap: 12px; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",