@primestyleai/tryon 5.7.8 → 5.7.9

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.
@@ -9992,7 +9992,7 @@ function isImperial(locale) {
9992
9992
  function cx(base, override) {
9993
9993
  return override ? `${base} ${override}` : base;
9994
9994
  }
9995
- const STYLES = `
9995
+ const STYLES$1 = `
9996
9996
  /* Variable defaults must live on BOTH the root (for the trigger button)
9997
9997
  and the overlay (which is React-portaled to <body> and therefore not
9998
9998
  a descendant of .ps-tryon-root, so the cascade is broken). Without
@@ -19604,7 +19604,7 @@ if (typeof document !== "undefined") {
19604
19604
  if (!document.getElementById(id2)) {
19605
19605
  const el2 = document.createElement("style");
19606
19606
  el2.id = id2;
19607
- el2.textContent = STYLES;
19607
+ el2.textContent = STYLES$1;
19608
19608
  document.head.appendChild(el2);
19609
19609
  }
19610
19610
  }
@@ -20991,6 +20991,339 @@ function PrimeStyleTryonInner({
20991
20991
  function PrimeStyleTryon(props) {
20992
20992
  return /* @__PURE__ */ jsxRuntimeExports.jsx(PrimeStyleTryonInner, { ...props });
20993
20993
  }
20994
+ const STYLES = `
20995
+ .ps-sg-btn {
20996
+ display: inline-flex;
20997
+ align-items: center;
20998
+ gap: 6px;
20999
+ padding: 8px 14px;
21000
+ margin: 0 0 10px 0;
21001
+ background: transparent;
21002
+ border: 1px solid rgba(0, 0, 0, 0.12);
21003
+ border-radius: 8px;
21004
+ color: var(--ps-sg-accent, #2154EF);
21005
+ font-size: 13px;
21006
+ font-weight: 600;
21007
+ font-family: inherit;
21008
+ cursor: pointer;
21009
+ transition: background 0.15s, border-color 0.15s;
21010
+ }
21011
+ .ps-sg-btn:hover {
21012
+ background: rgba(33, 84, 239, 0.04);
21013
+ border-color: var(--ps-sg-accent, #2154EF);
21014
+ }
21015
+ .ps-sg-btn svg { width: 14px; height: 14px; }
21016
+ .ps-sg-overlay {
21017
+ position: fixed; inset: 0;
21018
+ background: rgba(15, 23, 42, 0.55);
21019
+ display: flex; align-items: center; justify-content: center;
21020
+ z-index: 99999;
21021
+ padding: 16px;
21022
+ opacity: 0;
21023
+ animation: ps-sg-fadein 0.18s ease forwards;
21024
+ }
21025
+ @keyframes ps-sg-fadein { to { opacity: 1; } }
21026
+ .ps-sg-modal {
21027
+ background: #FFFFFF;
21028
+ border-radius: 14px;
21029
+ width: 100%;
21030
+ max-width: 720px;
21031
+ max-height: 88vh;
21032
+ overflow: hidden;
21033
+ display: flex;
21034
+ flex-direction: column;
21035
+ box-shadow: 0 24px 60px rgba(15, 23, 42, 0.25);
21036
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
21037
+ color: #1f2937;
21038
+ }
21039
+ .ps-sg-header {
21040
+ display: flex; align-items: center; justify-content: space-between;
21041
+ padding: 18px 22px 14px;
21042
+ border-bottom: 1px solid rgba(15, 23, 42, 0.08);
21043
+ }
21044
+ .ps-sg-title {
21045
+ margin: 0;
21046
+ font-size: 17px;
21047
+ font-weight: 700;
21048
+ }
21049
+ .ps-sg-toggle {
21050
+ display: inline-flex;
21051
+ background: rgba(15, 23, 42, 0.06);
21052
+ border-radius: 8px;
21053
+ padding: 3px;
21054
+ margin-right: 12px;
21055
+ }
21056
+ .ps-sg-toggle button {
21057
+ padding: 5px 14px;
21058
+ background: transparent;
21059
+ border: none;
21060
+ border-radius: 6px;
21061
+ font-size: 11px;
21062
+ font-weight: 700;
21063
+ cursor: pointer;
21064
+ color: rgba(15, 23, 42, 0.55);
21065
+ text-transform: uppercase;
21066
+ letter-spacing: 0.04em;
21067
+ font-family: inherit;
21068
+ }
21069
+ .ps-sg-toggle button.ps-sg-active {
21070
+ background: #FFFFFF;
21071
+ color: #1f2937;
21072
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
21073
+ }
21074
+ .ps-sg-close {
21075
+ background: transparent;
21076
+ border: none;
21077
+ color: rgba(15, 23, 42, 0.55);
21078
+ cursor: pointer;
21079
+ padding: 4px;
21080
+ border-radius: 6px;
21081
+ font-family: inherit;
21082
+ }
21083
+ .ps-sg-close:hover { background: rgba(15, 23, 42, 0.06); }
21084
+ .ps-sg-close svg { width: 18px; height: 18px; }
21085
+ .ps-sg-body {
21086
+ padding: 18px 22px 22px;
21087
+ overflow-y: auto;
21088
+ flex: 1;
21089
+ }
21090
+ .ps-sg-section { margin-bottom: 22px; }
21091
+ .ps-sg-section:last-child { margin-bottom: 0; }
21092
+ .ps-sg-section-title {
21093
+ margin: 0 0 6px;
21094
+ font-size: 14px;
21095
+ font-weight: 700;
21096
+ color: #0f172a;
21097
+ }
21098
+ .ps-sg-section-desc {
21099
+ margin: 0 0 10px;
21100
+ font-size: 12px;
21101
+ color: rgba(15, 23, 42, 0.6);
21102
+ }
21103
+ .ps-sg-table-wrap {
21104
+ overflow-x: auto;
21105
+ border: 1px solid rgba(15, 23, 42, 0.08);
21106
+ border-radius: 10px;
21107
+ }
21108
+ .ps-sg-table {
21109
+ width: 100%;
21110
+ border-collapse: collapse;
21111
+ font-size: 13px;
21112
+ }
21113
+ .ps-sg-table thead th {
21114
+ background: rgba(15, 23, 42, 0.04);
21115
+ padding: 10px 12px;
21116
+ text-align: left;
21117
+ font-weight: 700;
21118
+ font-size: 11px;
21119
+ text-transform: uppercase;
21120
+ letter-spacing: 0.04em;
21121
+ color: rgba(15, 23, 42, 0.6);
21122
+ border-bottom: 1px solid rgba(15, 23, 42, 0.08);
21123
+ }
21124
+ .ps-sg-table tbody td {
21125
+ padding: 10px 12px;
21126
+ border-bottom: 1px solid rgba(15, 23, 42, 0.05);
21127
+ }
21128
+ .ps-sg-table tbody tr:last-child td { border-bottom: none; }
21129
+ .ps-sg-htm {
21130
+ margin-top: 18px;
21131
+ padding: 14px 16px;
21132
+ background: rgba(33, 84, 239, 0.05);
21133
+ border-radius: 10px;
21134
+ border: 1px solid rgba(33, 84, 239, 0.12);
21135
+ }
21136
+ .ps-sg-htm-title {
21137
+ font-size: 12px;
21138
+ font-weight: 700;
21139
+ text-transform: uppercase;
21140
+ letter-spacing: 0.04em;
21141
+ color: var(--ps-sg-accent, #2154EF);
21142
+ margin-bottom: 8px;
21143
+ }
21144
+ .ps-sg-htm ul {
21145
+ margin: 0;
21146
+ padding-left: 18px;
21147
+ font-size: 12px;
21148
+ line-height: 1.6;
21149
+ color: #1f2937;
21150
+ }
21151
+ `;
21152
+ let stylesInjected = false;
21153
+ function injectStyles() {
21154
+ if (stylesInjected) return;
21155
+ const tag = document.createElement("style");
21156
+ tag.id = "ps-sg-styles";
21157
+ tag.textContent = STYLES;
21158
+ document.head.appendChild(tag);
21159
+ stylesInjected = true;
21160
+ }
21161
+ const RULER_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z"/><path d="m14.5 12.5 2-2"/><path d="m11.5 9.5 2-2"/><path d="m8.5 6.5 2-2"/><path d="m17.5 15.5 2-2"/></svg>`;
21162
+ const X_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
21163
+ function cmToInch(value) {
21164
+ return value.replace(/[\d]+(?:\.\d+)?/g, (m2) => {
21165
+ const n2 = parseFloat(m2);
21166
+ if (isNaN(n2)) return m2;
21167
+ const inches = n2 / 2.54;
21168
+ return inches % 1 === 0 ? inches.toFixed(0) : inches.toFixed(1);
21169
+ }).replace(/\s*cm\s*/gi, "");
21170
+ }
21171
+ function inchToCm(value) {
21172
+ return value.replace(/[\d]+(?:\.\d+)?/g, (m2) => {
21173
+ const n2 = parseFloat(m2);
21174
+ return isNaN(n2) ? m2 : String(Math.round(n2 * 2.54));
21175
+ }).replace(/[\u0022\u2033\u201D″"]/g, "").trim();
21176
+ }
21177
+ function normalizeToSections(guide) {
21178
+ if (Array.isArray(guide.sections) && guide.sections.length > 0) {
21179
+ return guide.sections;
21180
+ }
21181
+ if (guide.headers && guide.rows) {
21182
+ return [
21183
+ {
21184
+ name: guide.title || "Size Guide",
21185
+ headers: guide.headers,
21186
+ rows: guide.rows
21187
+ }
21188
+ ];
21189
+ }
21190
+ return [];
21191
+ }
21192
+ function buildSectionTable(section, unit, sourceUnit) {
21193
+ const allKeys = section.headers && section.headers.length > 0 ? section.headers : Object.keys(section.rows[0] || {});
21194
+ const colKeys = allKeys.filter((k2) => {
21195
+ const lower = k2.toLowerCase();
21196
+ const hasIn = lower.includes("(in)") || lower.endsWith(" in");
21197
+ const hasCm = lower.includes("(cm)") || lower.endsWith(" cm");
21198
+ if (hasIn) {
21199
+ const base = lower.replace(/\s*\(in\)\s*/, "").replace(/\s+in$/, "").trim();
21200
+ const cmExists = allKeys.some((o) => {
21201
+ const ol2 = o.toLowerCase();
21202
+ return ol2 !== lower && ol2.includes(base) && (ol2.includes("(cm)") || ol2.endsWith(" cm"));
21203
+ });
21204
+ return cmExists ? unit === "in" : true;
21205
+ }
21206
+ if (hasCm) {
21207
+ const base = lower.replace(/\s*\(cm\)\s*/, "").replace(/\s+cm$/, "").trim();
21208
+ const inExists = allKeys.some((o) => {
21209
+ const ol2 = o.toLowerCase();
21210
+ return ol2 !== lower && ol2.includes(base) && (ol2.includes("(in)") || ol2.endsWith(" in"));
21211
+ });
21212
+ return inExists ? unit === "cm" : true;
21213
+ }
21214
+ return true;
21215
+ });
21216
+ const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
21217
+ const displayHeader = (key) => escapeHtml(key.replace(/\s*\((cm|in)\)\s*$/i, "").trim());
21218
+ const convertCell = (value, key) => {
21219
+ const keyLower = key.toLowerCase();
21220
+ const headerHasUnit = /\((cm|in)\)|\s+(cm|in)$/.test(keyLower);
21221
+ if (headerHasUnit) return value;
21222
+ if (/^[a-z\s]+$/i.test(value)) return value;
21223
+ if (sourceUnit === "cm" && unit === "in") return cmToInch(value);
21224
+ if (sourceUnit === "in" && unit === "cm") return inchToCm(value);
21225
+ return value;
21226
+ };
21227
+ const head = colKeys.map((k2) => `<th>${displayHeader(k2)}</th>`).join("");
21228
+ const body = section.rows.map((row) => {
21229
+ const cells = colKeys.map((k2) => {
21230
+ const raw = row[k2] ?? row[k2.toLowerCase()] ?? "";
21231
+ return `<td>${escapeHtml(convertCell(String(raw), k2))}</td>`;
21232
+ }).join("");
21233
+ return `<tr>${cells}</tr>`;
21234
+ }).join("");
21235
+ return `
21236
+ <div class="ps-sg-section">
21237
+ <h3 class="ps-sg-section-title">${escapeHtml(section.name)}</h3>
21238
+ ${section.description ? `<p class="ps-sg-section-desc">${escapeHtml(section.description)}</p>` : ""}
21239
+ <div class="ps-sg-table-wrap">
21240
+ <table class="ps-sg-table"><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table>
21241
+ </div>
21242
+ </div>
21243
+ `;
21244
+ }
21245
+ function createSizeGuideButton(parent, sizeGuide, options = {}) {
21246
+ injectStyles();
21247
+ const sections = normalizeToSections(sizeGuide);
21248
+ if (sections.length === 0) {
21249
+ console.log("[primestyle-tryon] size guide has no sections, skipping button");
21250
+ return () => {
21251
+ };
21252
+ }
21253
+ const sourceUnit = sizeGuide.unit === "in" ? "in" : "cm";
21254
+ const btn = document.createElement("button");
21255
+ btn.type = "button";
21256
+ btn.className = "ps-sg-btn";
21257
+ btn.innerHTML = `${RULER_ICON}<span>${options.label || "Size Guide"}</span>`;
21258
+ if (options.accentColor) {
21259
+ btn.style.setProperty("--ps-sg-accent", options.accentColor);
21260
+ }
21261
+ let unit = sourceUnit;
21262
+ let overlayEl = null;
21263
+ function renderModal() {
21264
+ const overlay = document.createElement("div");
21265
+ overlay.className = "ps-sg-overlay";
21266
+ overlay.innerHTML = `
21267
+ <div class="ps-sg-modal" role="dialog" aria-modal="true">
21268
+ <div class="ps-sg-header">
21269
+ <h2 class="ps-sg-title">${(sizeGuide.title || "Size Guide").replace(/</g, "&lt;")}</h2>
21270
+ <div style="display:flex;align-items:center">
21271
+ <div class="ps-sg-toggle" role="group" aria-label="Unit">
21272
+ <button type="button" data-unit="cm" class="${unit === "cm" ? "ps-sg-active" : ""}">CM</button>
21273
+ <button type="button" data-unit="in" class="${unit === "in" ? "ps-sg-active" : ""}">IN</button>
21274
+ </div>
21275
+ <button class="ps-sg-close" type="button" aria-label="Close">${X_ICON}</button>
21276
+ </div>
21277
+ </div>
21278
+ <div class="ps-sg-body">
21279
+ ${sections.map((s) => buildSectionTable(s, unit, sourceUnit)).join("")}
21280
+ ${sizeGuide.howToMeasure && sizeGuide.howToMeasure.length > 0 ? `<div class="ps-sg-htm">
21281
+ <div class="ps-sg-htm-title">How to measure</div>
21282
+ <ul>${sizeGuide.howToMeasure.map((h) => `<li>${h.replace(/</g, "&lt;")}</li>`).join("")}</ul>
21283
+ </div>` : ""}
21284
+ </div>
21285
+ </div>
21286
+ `;
21287
+ if (options.accentColor) {
21288
+ overlay.style.setProperty("--ps-sg-accent", options.accentColor);
21289
+ }
21290
+ document.body.appendChild(overlay);
21291
+ overlayEl = overlay;
21292
+ document.body.style.overflow = "hidden";
21293
+ overlay.addEventListener("click", (e) => {
21294
+ if (e.target === overlay) close();
21295
+ });
21296
+ overlay.querySelector(".ps-sg-close")?.addEventListener("click", close);
21297
+ overlay.querySelectorAll(".ps-sg-toggle button").forEach((b) => {
21298
+ b.addEventListener("click", () => {
21299
+ const next = b.getAttribute("data-unit");
21300
+ if (next === unit) return;
21301
+ unit = next;
21302
+ close();
21303
+ renderModal();
21304
+ });
21305
+ });
21306
+ const onKey = (e) => {
21307
+ if (e.key === "Escape") close();
21308
+ };
21309
+ document.addEventListener("keydown", onKey);
21310
+ overlay.__onKey = onKey;
21311
+ }
21312
+ function close() {
21313
+ if (!overlayEl) return;
21314
+ const onKey = overlayEl.__onKey;
21315
+ if (onKey) document.removeEventListener("keydown", onKey);
21316
+ overlayEl.remove();
21317
+ overlayEl = null;
21318
+ document.body.style.overflow = "";
21319
+ }
21320
+ btn.addEventListener("click", renderModal);
21321
+ parent.parentElement?.insertBefore(btn, parent);
21322
+ return () => {
21323
+ btn.remove();
21324
+ close();
21325
+ };
21326
+ }
20994
21327
  const TAG = "[primestyle-tryon]";
20995
21328
  console.log(`${TAG} bundle loaded — version 5.7.x storefront entry`);
20996
21329
  const MOUNTED = /* @__PURE__ */ new WeakMap();
@@ -21073,6 +21406,17 @@ async function mount(el2) {
21073
21406
  props.sizeGuideData = fetched;
21074
21407
  }
21075
21408
  }
21409
+ if (props.sizeGuideData) {
21410
+ try {
21411
+ const buttonStyles = props.buttonStyles;
21412
+ createSizeGuideButton(el2, props.sizeGuideData, {
21413
+ accentColor: buttonStyles?.backgroundColor
21414
+ });
21415
+ console.log(`${TAG} ✓ size guide button mounted`);
21416
+ } catch (err) {
21417
+ console.warn(`${TAG} size guide button failed`, err);
21418
+ }
21419
+ }
21076
21420
  try {
21077
21421
  const root = createRoot(el2);
21078
21422
  root.render(reactExports.createElement(PrimeStyleTryon, props));
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Vanilla JS size-guide button + modal for the Shopify storefront.
3
+ *
4
+ * Mirrors the prime-main SizeGuideModal design but built without React
5
+ * so it can attach next to (not inside) the React-mounted SDK root.
6
+ *
7
+ * Usage:
8
+ * createSizeGuideButton(parentEl, sizeGuide, { buttonStyles, ... });
9
+ *
10
+ * The button is inserted as a sibling above the React root. Clicking
11
+ * it appends a modal to <body> with a size-chart table, cm/in toggle,
12
+ * and "How to measure" hints.
13
+ */
14
+ interface RawSection {
15
+ name: string;
16
+ description?: string;
17
+ headers: string[];
18
+ rows: Array<Record<string, string>>;
19
+ }
20
+ interface RawSizeGuide {
21
+ title?: string;
22
+ headers?: string[];
23
+ rows?: Array<Record<string, string>>;
24
+ unit?: "cm" | "in";
25
+ sections?: RawSection[];
26
+ howToMeasure?: string[];
27
+ }
28
+ interface ButtonOptions {
29
+ label?: string;
30
+ accentColor?: string;
31
+ }
32
+ /**
33
+ * Create a "Size Guide" button and insert it as a sibling above the
34
+ * given parent element. Returns a teardown function that removes the
35
+ * button + any open modal.
36
+ */
37
+ export declare function createSizeGuideButton(parent: Element, sizeGuide: RawSizeGuide, options?: ButtonOptions): () => void;
38
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "5.7.8",
3
+ "version": "5.7.9",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",