@primestyleai/tryon 5.10.104 → 5.10.105

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.
@@ -21,6 +21,13 @@ export declare function setLastCompletedProduct(productId: string | null, produc
21
21
  * the size-result view. The cart-hook then fires a SIZE_RECOMMENDATION_ACCEPTED
22
22
  * event when this product gets added to cart — a clearer "user committed"
23
23
  * signal than "user clicked a pill once".
24
+ *
25
+ * Side-effect: proactively stamp the current cart with `attributes[ps_session]`
26
+ * via `/cart/update.js`. This is the primary attribution path now — works
27
+ * regardless of how the buyer eventually checks out (Ajax cart, Buy It Now,
28
+ * Shop Pay), because the attribute is already on the cart object before
29
+ * the buyer adds anything. The legacy fetch/form-submit interception below
30
+ * stays as a defense-in-depth fallback for older themes.
24
31
  */
25
32
  export declare function setLastSizeSelection(input: {
26
33
  productId: string | null;
@@ -1,4 +1,46 @@
1
1
  "use client";
2
+ const TAG$3 = "[primestyle-shadow]";
3
+ const collected = [];
4
+ if (typeof document !== "undefined" && document.head) {
5
+ let shouldCapture = function(node) {
6
+ return node instanceof HTMLStyleElement;
7
+ };
8
+ const origAppend = document.head.appendChild.bind(document.head);
9
+ const origInsertBefore = document.head.insertBefore.bind(document.head);
10
+ document.head.appendChild = function(node) {
11
+ if (shouldCapture(node)) {
12
+ collected.push(node);
13
+ console.log(`${TAG$3} captured style tag (${node.textContent?.length ?? 0} chars)`);
14
+ }
15
+ return origAppend(node);
16
+ };
17
+ document.head.insertBefore = function(node, ref) {
18
+ if (shouldCapture(node)) {
19
+ collected.push(node);
20
+ console.log(`${TAG$3} captured style tag via insertBefore (${node.textContent?.length ?? 0} chars)`);
21
+ }
22
+ return origInsertBefore(node, ref);
23
+ };
24
+ }
25
+ function getCollectedCss() {
26
+ const parts = [];
27
+ for (const el2 of collected) {
28
+ const text = el2.textContent;
29
+ if (text) parts.push(text);
30
+ }
31
+ return parts.join("\n\n");
32
+ }
33
+ function injectStylesIntoShadow(shadow) {
34
+ const css = getCollectedCss();
35
+ if (!css) {
36
+ console.warn(`${TAG$3} no SDK styles collected — widget may render unstyled`);
37
+ return;
38
+ }
39
+ const style = document.createElement("style");
40
+ style.setAttribute("data-primestyle", "sdk-styles");
41
+ style.textContent = css;
42
+ shadow.appendChild(style);
43
+ }
2
44
  var react = { exports: {} };
3
45
  var react_production_min = {};
4
46
  /**
@@ -19418,14 +19460,6 @@ function ProductPhotoCarouselCard({
19418
19460
  slide.push(photos[(start + slide.length) % photos.length]);
19419
19461
  }
19420
19462
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-photo-strip", role: "group", "aria-label": t2("Product photos"), children: [
19421
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-photo-strip-head", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ps-tryon-photo-strip-badge", children: [
19422
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
19423
- /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
19424
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "9", cy: "9", r: "2" }),
19425
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
19426
- ] }),
19427
- t2("Gallery")
19428
- ] }) }),
19429
19463
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-photo-strip-row", children: slide.map((src, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-photo-strip-cell", children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src, alt: productTitle || "", draggable: false }) }, `${groupIdx}-${i}`)) }, groupIdx),
19430
19464
  totalGroups > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-photo-strip-dots", "aria-hidden": "true", children: Array.from({ length: totalGroups }).map((_, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `ps-tryon-photo-strip-dot${i === groupIdx ? " is-active" : ""}` }, i)) })
19431
19465
  ] });
@@ -20066,103 +20100,26 @@ function SectionDetailView({
20066
20100
  return details.map((m2) => {
20067
20101
  if (isFromLength.has(m2.measurement)) {
20068
20102
  const userNum2 = userMeasurements[m2.measurement.toLowerCase()] || pNumFn(m2.userValue);
20069
- const activeLength = selectedLength || effectiveRecLength || m2.chartRange;
20070
- if (!lengthEntry) {
20071
- return { area: m2.measurement + " (" + activeLength + ")", userNum: userNum2, chartLabel: activeLength, fit: "good", isLength: true };
20072
- }
20073
- const sec = lengthEntry.section;
20074
- const measLc = m2.measurement.toLowerCase();
20075
- const isHeight = measLc === "height";
20076
- const sizeCol = sec.headers.findIndex((h) => /size|length/i.test(h.trim()));
20077
- const userIsInches = unitLbl === "in";
20078
- let targetColIdx = -1;
20079
- let colIsCm = false;
20080
- if (isHeight) {
20081
- const cmColIdx = sec.headers.findIndex((h) => /cm|\(cm\)|height.*cm/i.test(h.toLowerCase()));
20082
- const genericColIdx = sec.headers.findIndex((h) => /height|altezza|estatura/i.test(h.toLowerCase()) && !/cm/i.test(h));
20083
- targetColIdx = cmColIdx >= 0 ? cmColIdx : genericColIdx;
20084
- colIsCm = targetColIdx === cmColIdx;
20085
- } else {
20086
- targetColIdx = sec.headers.findIndex((h) => {
20087
- const hLc = h.toLowerCase().replace(/\s*\(.*?\)\s*/g, "").trim();
20088
- if (!hLc) return false;
20089
- return hLc === measLc || hLc.includes(measLc) || measLc.includes(hLc);
20090
- });
20091
- colIsCm = targetColIdx >= 0 ? /cm/i.test(sec.headers[targetColIdx] || "") : false;
20092
- }
20093
- const sIdx = sizeCol >= 0 ? sizeCol : 0;
20094
- const hIdx = targetColIdx >= 0 ? targetColIdx : -1;
20095
- const activeLengthForLookup = (() => {
20096
- const al2 = (activeLength || "").toLowerCase().trim();
20097
- if (/big.*tall|tall/.test(al2)) return "Long";
20098
- if (/^big$/.test(al2)) return "Regular";
20099
- return activeLength;
20100
- })();
20101
- const alLc = (activeLengthForLookup || "").toLowerCase().trim();
20102
- const matchRow = sec.rows.find((r2) => cellValFn(r2, sIdx, sec.headers[sIdx]) === activeLength) || sec.rows.find((r2) => cellValFn(r2, sIdx, sec.headers[sIdx]).trim().toLowerCase() === alLc) || null;
20103
- let chartLabel2 = activeLength;
20104
- let fit2 = "good";
20105
- if (matchRow && hIdx >= 0) {
20106
- const rangeStr = cellValFn(matchRow, hIdx, sec.headers[hIdx]);
20107
- if (rangeStr) {
20108
- const { min: rMinRaw, max: rMaxRaw } = pRangeFn(rangeStr);
20109
- if (rMinRaw > 0 && rMaxRaw > 0) {
20110
- const userInColUnit = colIsCm && userIsInches ? +(userNum2 * 2.54).toFixed(1) : !colIsCm && !userIsInches ? +(userNum2 * 2.54).toFixed(1) : userNum2;
20111
- const range2 = rMaxRaw - rMinRaw;
20112
- const threshold2 = range2 > 0 ? range2 * 0.5 : rMinRaw * 0.05 || 3;
20113
- const tol = colIsCm ? 2.54 : 1;
20114
- if (userInColUnit > rMinRaw - tol && userInColUnit < rMaxRaw + tol) fit2 = "good";
20115
- else if (userInColUnit < rMinRaw) {
20116
- const diff = rMinRaw - userInColUnit;
20117
- fit2 = diff > threshold2 * 2 ? "too-long" : diff > threshold2 ? "long" : "a-bit-long";
20118
- } else {
20119
- const diff = userInColUnit - rMaxRaw;
20120
- fit2 = diff > threshold2 * 2 ? "too-short" : diff > threshold2 ? "short" : "a-bit-short";
20121
- }
20122
- const needsConvert = colIsCm && userIsInches || !colIsCm && !userIsInches;
20123
- const rMinUser = needsConvert ? colIsCm ? +(rMinRaw / 2.54).toFixed(1) : +(rMinRaw * 2.54).toFixed(1) : rMinRaw;
20124
- const rMaxUser = needsConvert ? colIsCm ? +(rMaxRaw / 2.54).toFixed(1) : +(rMaxRaw * 2.54).toFixed(1) : rMaxRaw;
20125
- chartLabel2 = rMinUser === rMaxUser ? `${rMinUser}` : `${rMinUser}-${rMaxUser}`;
20126
- } else {
20127
- chartLabel2 = rangeStr;
20128
- }
20129
- }
20130
- }
20131
- return { area: m2.measurement + " (" + activeLength + ")", userNum: userNum2, chartLabel: cleanNumFn(chartLabel2), fit: fit2, isLength: true };
20103
+ return {
20104
+ area: m2.measurement,
20105
+ userNum: userNum2,
20106
+ chartLabel: cleanNumFn(m2.chartRange),
20107
+ fit: m2.fit || "good",
20108
+ isLength: true
20109
+ };
20132
20110
  }
20133
20111
  const userNum = userMeasurements[m2.measurement.toLowerCase()] || pNumFn(m2.userValue);
20134
- let { min: rMin, max: rMax } = pRangeFn(m2.chartRange);
20135
- let chartLabel = m2.chartRange;
20136
- const alt = chartRangeFor(m2.measurement, displaySize);
20137
- if (alt) {
20138
- chartLabel = alt.range;
20139
- rMin = alt.min;
20140
- rMax = alt.max;
20141
- }
20142
- const range = rMax - rMin;
20143
- const threshold = range > 0 ? range * 0.5 : rMin * 0.05 || 3;
20144
20112
  const measLower = m2.measurement.toLowerCase();
20145
20113
  const isDirectional = /length|inseam|sleeve|hem|rise/.test(measLower);
20146
- let fit;
20147
- const perfectTol = chartUnit === "cm" ? 2.54 : chartUnit === "mm" ? 25.4 : 1;
20148
- const lowBound = rMin - perfectTol;
20149
- const highBound = rMax + perfectTol;
20150
- if (userNum > lowBound && userNum < highBound) {
20151
- fit = "good";
20152
- } else if (isDirectional) {
20153
- const diff = userNum > rMax ? userNum - rMax : rMin - userNum;
20154
- const bucket = diff > threshold * 2 ? "too-" : diff > threshold ? "" : "a-bit-";
20155
- fit = bucket + (userNum > rMax ? "short" : "long");
20156
- } else if (userNum < rMin) {
20157
- const diff = rMin - userNum;
20158
- fit = diff > threshold * 2 ? "too-loose" : diff > threshold ? "loose" : "a-bit-loose";
20159
- } else {
20160
- const diff = userNum - rMax;
20161
- fit = diff > threshold * 2 ? "too-tight" : diff > threshold ? "tight" : "a-bit-tight";
20162
- }
20163
- return { area: m2.measurement, userNum, chartLabel: cleanNumFn(chartLabel), fit, isLength: isDirectional };
20114
+ return {
20115
+ area: m2.measurement,
20116
+ userNum,
20117
+ chartLabel: cleanNumFn(m2.chartRange),
20118
+ fit: m2.fit || "good",
20119
+ isLength: isDirectional
20120
+ };
20164
20121
  });
20165
- }, [sectionResult, lengthEntry, userMeasurements, displaySize, recSize, chartRangeFor, selectedLength, recLength, renderRaw]);
20122
+ }, [sectionResult, lengthEntry, userMeasurements, renderRaw]);
20166
20123
  const goodCount = fitRows.filter(
20167
20124
  (r2) => r2.fit === "good" || r2.fit === "a-bit-tight" || r2.fit === "a-bit-loose"
20168
20125
  ).length;
@@ -30376,10 +30333,29 @@ async function mount(el2) {
30376
30333
  props.sizeGuideData = fetched;
30377
30334
  }
30378
30335
  }
30336
+ let shadow;
30337
+ try {
30338
+ shadow = el2.shadowRoot ?? el2.attachShadow({ mode: "open" });
30339
+ injectStylesIntoShadow(shadow);
30340
+ } catch (err) {
30341
+ console.warn(`${TAG} shadow attach failed — falling back to direct mount`, err);
30342
+ shadow = el2;
30343
+ }
30344
+ let mountTarget;
30345
+ if (shadow instanceof ShadowRoot) {
30346
+ mountTarget = shadow.querySelector("[data-primestyle-mount]") ?? (() => {
30347
+ const c = document.createElement("div");
30348
+ c.setAttribute("data-primestyle-mount", "");
30349
+ shadow.appendChild(c);
30350
+ return c;
30351
+ })();
30352
+ } else {
30353
+ mountTarget = shadow;
30354
+ }
30379
30355
  if (props.sizeGuideData) {
30380
30356
  try {
30381
30357
  const buttonStyles = props.buttonStyles;
30382
- createSizeGuideButton(el2, props.sizeGuideData, {
30358
+ createSizeGuideButton(mountTarget, props.sizeGuideData, {
30383
30359
  accentColor: buttonStyles?.backgroundColor
30384
30360
  });
30385
30361
  console.log(`${TAG} ✓ size guide button mounted`);
@@ -30388,10 +30364,10 @@ async function mount(el2) {
30388
30364
  }
30389
30365
  }
30390
30366
  try {
30391
- const root = createRoot(el2);
30367
+ const root = createRoot(mountTarget);
30392
30368
  root.render(reactExports.createElement(PrimeStyleTryon, props));
30393
30369
  MOUNTED.set(el2, root);
30394
- console.log(`${TAG} ✓ mounted React component`);
30370
+ console.log(`${TAG} ✓ mounted React component (shadow-isolated)`);
30395
30371
  maybeFireProductView(el2);
30396
30372
  } catch (err) {
30397
30373
  console.error(`${TAG} ✗ React mount failed`, err);
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shadow-DOM style collector.
3
+ *
4
+ * Vite injects all CSS imported by the storefront bundle as <style>
5
+ * tags appended to document.head at module-eval time. Theme app
6
+ * extensions render into the merchant's theme DOM, so those styles
7
+ * coexist with the merchant's theme CSS — and the theme's CSS leaks
8
+ * into our widget (table borders, button resets, font-family, etc).
9
+ *
10
+ * To isolate the widget inside a shadow root we need our SDK styles
11
+ * inside that shadow root, NOT in document.head. The cleanest way to
12
+ * intercept vite's runtime injection is to monkey-patch
13
+ * `document.head.appendChild` before our other imports evaluate, so
14
+ * every <style> tag the SDK emits during module load gets captured
15
+ * here and re-injected into each shadow root we mount later.
16
+ *
17
+ * IMPORTANT: this file MUST be imported as the *first* import of
18
+ * `src/storefront/index.ts`, before React or any CSS-importing module.
19
+ * ES module evaluation order is import-tree depth-first, so a
20
+ * side-effect import at the top of the entry runs before the rest.
21
+ */
22
+ /**
23
+ * Returns CSS text concatenated from every <style> tag captured at
24
+ * module load + any <style> tags that were already in document.head
25
+ * matching SDK selector patterns (defense-in-depth for cases where
26
+ * patching missed an injection).
27
+ */
28
+ export declare function getCollectedCss(): string;
29
+ /**
30
+ * Inject the SDK's collected styles into a shadow root so the widget
31
+ * looks identical regardless of theme. Idempotent — call once per
32
+ * shadow root after attachShadow().
33
+ */
34
+ export declare function injectStylesIntoShadow(shadow: ShadowRoot): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "5.10.104",
3
+ "version": "5.10.105",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",