@gemboss/gem-framework 0.19.0 → 0.20.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/README.md +30 -1
- package/dist/SOURCE.json +3 -3
- package/dist/gem-runtime.js +4 -1
- package/dist/index.js +855 -13
- package/dist/plugins/delivery-estimate.d.ts +9 -0
- package/dist/plugins/delivery-estimate.js +1 -0
- package/dist/plugins/personalize.d.ts +9 -0
- package/dist/plugins/personalize.js +1 -0
- package/dist/plugins/print-preview.d.ts +9 -0
- package/dist/plugins/print-preview.js +1 -0
- package/dist/pod/core.d.ts +26 -0
- package/dist/pod/core.js +1 -0
- package/dist/pod/delivery-estimate.d.ts +28 -0
- package/dist/pod/delivery-estimate.js +1 -0
- package/dist/pod/personalize.d.ts +25 -0
- package/dist/pod/personalize.js +1 -0
- package/dist/pod/print-preview.d.ts +8 -0
- package/dist/pod/print-preview.js +1 -0
- package/dist/pod-buybox.css +55 -0
- package/dist/section-gate.js +161 -7
- package/package.json +10 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/delivery-estimate: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "delivery-estimate";
|
|
6
|
+
export declare const readyEvent: "gem:delivery-estimate:ready";
|
|
7
|
+
/** Bind every "delivery-estimate" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{mountPlugin as d}from"../runtime-core.js";import{mountDeliveryEstimate as g}from"../pod/delivery-estimate.js";const f="delivery-estimate",N="gem:delivery-estimate:ready";function p({document:e,window:t,target:x,mark:i,canListen:n,listen:m,registerPlugin:s,registerNoopPlugin:E,onUnmount:u}){s("delivery-estimate",({api:o})=>{const a=g({window:t,document:e,on:(r,l,y,c)=>{n(r)&&m(r,l,y,c)},mark:i,api:o},u);o.emit("gem:delivery-estimate:ready",{source:"gem-runtime",count:a})})}function v(e,t){return d(f,p,e,t)}var b=v;export{b as default,f as key,v as mount,N as readyEvent};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/personalize: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "personalize";
|
|
6
|
+
export declare const readyEvent: "gem:personalize:ready";
|
|
7
|
+
/** Bind every "personalize" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{mountPlugin as d}from"../runtime-core.js";import{mountPersonalize as u}from"../pod/personalize.js";const z="personalize",w="gem:personalize:ready";function _({document:e,window:o,target:v,mark:t,canListen:n,listen:i,registerPlugin:l,registerNoopPlugin:x,onUnmount:p}){l("personalize",({api:a})=>{const s=u({window:o,document:e,on:(r,m,g,c)=>{n(r)&&i(r,m,g,c)},mark:t,api:a,css:'.gem-el-product-personalization[data-gem-personalize-invalid] .gem-el-product-personalization__input,.gem-el-product-personalization[data-gem-personalize-invalid] .gem-el-product-personalization__upload,.gem-el-product-personalization[data-gem-personalize-invalid] .gem-el-product-personalization__swatch,.gem-el-product-personalization[data-gem-personalize-invalid] .gem-el-product-personalization__choice-image{border-color:var(--gem-color-danger)}.gem-el-product-personalization[data-gem-personalize-state="uploading"] .gem-el-product-personalization__upload{cursor:progress;opacity:0.7}.gem-el-product-personalization__count[data-gem-personalize-full]{color:var(--gem-color-text);font-weight:600}'},p);a.emit("gem:personalize:ready",{source:"gem-runtime",count:s})})}function f(e,o){return d(z,_,e,o)}var b=f;export{b as default,z as key,f as mount,w as readyEvent};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/print-preview: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "print-preview";
|
|
6
|
+
export declare const readyEvent: "gem:print-preview:ready";
|
|
7
|
+
/** Bind every "print-preview" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{mountPlugin as w}from"../runtime-core.js";import{mountPrintPreview as d}from"../pod/print-preview.js";const s="print-preview",k="gem:print-preview:ready";function u({document:e,window:t,target:h,mark:n,canListen:p,listen:o,registerPlugin:a,registerNoopPlugin:x,onUnmount:g}){a("print-preview",({api:r})=>{const l=d({window:t,document:e,on:(i,m,c,v)=>{p(i)&&o(i,m,c,v)},mark:n,api:r,css:'.gem-el-product-print-preview__text{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--gem-print-preview-lines,2);max-width:100%;margin:0;overflow:hidden;overflow-wrap:anywhere;font-family:var(--gem-print-preview-font,var(--gem-font-heading));font-size:calc(82cqh / var(--gem-print-preview-lines,2));line-height:1.1;color:var(--gem-print-preview-color,var(--gem-color-text));text-align:center;transform-origin:center}.gem-el-product-print-preview[data-gem-print-preview-align="left"] .gem-el-product-print-preview__area{place-items:center start}.gem-el-product-print-preview[data-gem-print-preview-align="left"] .gem-el-product-print-preview__text{text-align:start;transform-origin:left center}.gem-el-product-print-preview[data-gem-print-preview-align="right"] .gem-el-product-print-preview__area{place-items:center end}.gem-el-product-print-preview[data-gem-print-preview-align="right"] .gem-el-product-print-preview__text{text-align:end;transform-origin:right center}.gem-el-product-print-preview__photo{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}'},g);r.emit("gem:print-preview:ready",{source:"gem-runtime",count:l})})}function f(e,t){return w(s,u,e,t)}var P=f;export{P as default,s as key,f as mount,k as readyEvent};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Types for @gemboss/gem-framework/pod/core (core.js, copied minified into dist/pod/).
|
|
2
|
+
|
|
3
|
+
/** What a personalization field holds now (readField). */
|
|
4
|
+
export interface PersonalizeRead {
|
|
5
|
+
name: string;
|
|
6
|
+
type: string;
|
|
7
|
+
/** What the cart line gets: "" when blank; an upload's stored URL. */
|
|
8
|
+
value: string;
|
|
9
|
+
/** The picked option's hex colour, when it has one. */
|
|
10
|
+
color: string;
|
|
11
|
+
/** An upload's local picture (shown before the upload finishes), or its stored URL. */
|
|
12
|
+
preview: string;
|
|
13
|
+
/** An upload's storage key (written as `_<name>_upload`). */
|
|
14
|
+
key: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export declare const FIELD: "[data-gem-personalize]";
|
|
18
|
+
export declare function scopeOf(el: Element | null | undefined): Element | null;
|
|
19
|
+
export declare const uploads: WeakMap<Element, { uploading: boolean; preview: string; url: string; key: string }>;
|
|
20
|
+
export declare function fieldName(field: Element): string;
|
|
21
|
+
export declare function fieldType(field: Element): string;
|
|
22
|
+
export declare function controlsOf(field: Element): Array<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>;
|
|
23
|
+
export declare function hexColor(value: unknown): string;
|
|
24
|
+
export declare function readField(field: Element): PersonalizeRead;
|
|
25
|
+
export declare function readScope(scope: ParentNode | null | undefined): Record<string, PersonalizeRead>;
|
|
26
|
+
export declare function adoptCss(el: Element, key: string, sentinel: string, css: string): boolean;
|
package/dist/pod/core.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const y="[data-gem-personalize]",m=["[data-gem-personalize-scope]",'[data-gem-element="product"]','form[action*="/cart/add"]',"[data-gem-section-id]"];function b(e){for(const r of m){const o=e&&e.closest?e.closest(r):null;if(o)return o}return e&&e.ownerDocument?e.ownerDocument.documentElement:null}const x=new WeakMap,i=(e,r)=>e&&e.getAttribute?e.getAttribute(r):null,l=e=>typeof e=="string"?e.trim():"";function S(e){const r=l(i(e,"data-gem-personalize-name"));if(r)return r;const o=e.querySelector&&e.querySelector("input, select, textarea");return o?l(o.getAttribute("name")):""}function g(e){const r=l(i(e,"data-gem-personalize-type"));if(r)return r;const o=e.querySelector&&e.querySelector("input, select, textarea");return o?o.tagName==="SELECT"?"select":o.tagName==="TEXTAREA"?"textarea":o.type==="file"?"upload":o.type==="checkbox"?"checkbox":o.type==="radio"?"swatch":"text":"text"}const h=e=>Array.prototype.slice.call(e.querySelectorAll?e.querySelectorAll("input, select, textarea"):[]).filter(r=>r.type!=="hidden"),v=/^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i,p=e=>v.test(l(e))?l(e):"";function w(e){const r=S(e),o=g(e),n={name:r,type:o,value:"",color:"",preview:"",key:""},a=h(e);if(o==="upload"){const t=x.get(e);return t&&Object.assign(n,{value:t.url||"",preview:t.preview||t.url||"",key:t.key||""}),n}if(o==="checkbox"){const t=a.find(c=>c.type==="checkbox"&&c.checked);return t&&(n.value=t.value&&t.value!=="on"?t.value:"Yes"),n}if(o==="select"){const t=a.find(f=>f.tagName==="SELECT"),c=t&&t.selectedIndex>-1?t.options[t.selectedIndex]:null;return n.value=l(t?t.value:""),n.color=p(i(c,"data-gem-personalize-color")),n}const u=a.filter(t=>t.type==="radio");if(u.length){const t=u.find(c=>c.checked);if(t){n.value=l(t.value);const c=t.closest?t.closest("label"):null;n.color=p(i(t,"data-gem-personalize-color")||i(c,"data-gem-personalize-color"))}return n}const s=a[0];return n.value=l(s?s.value:""),n}function k(e){const r={},o=e&&e.querySelectorAll?e.querySelectorAll(y):[];return Array.prototype.forEach.call(o,n=>{const a=w(n);a.name&&(r[a.name]=n.hidden?{...a,value:"",color:"",preview:""}:a)}),r}const d=new WeakMap;function A(e,r,o,n){const a=e.ownerDocument,u=a&&a.defaultView;if(!n||!u)return!1;let s=d.get(a);if(s||d.set(a,s=new Set),s.has(r))return!1;s.add(r);try{if(u.getComputedStyle(e).getPropertyValue(o).trim())return!1;const t=new u.CSSStyleSheet;return t.replaceSync(n),a.adoptedStyleSheets=[...a.adoptedStyleSheets,t],!0}catch{return!1}}export{y as FIELD,A as adoptCss,h as controlsOf,S as fieldName,g as fieldType,p as hexColor,w as readField,k as readScope,b as scopeOf,x as uploads};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Types for @gemboss/gem-framework/pod/delivery-estimate (delivery-estimate.js, copied minified into dist/pod/).
|
|
2
|
+
|
|
3
|
+
export interface DeliverySpec {
|
|
4
|
+
/** Production days: "3-5", "3", or a number. */
|
|
5
|
+
production: string | number;
|
|
6
|
+
/** Shipping days, the same shapes. */
|
|
7
|
+
shipping: string | number;
|
|
8
|
+
/** Count Monday to Friday only (default true). */
|
|
9
|
+
businessDays?: boolean;
|
|
10
|
+
/** 0–23 in the store's time: past it, the order counts from the next day. */
|
|
11
|
+
cutoffHour?: number | string | null;
|
|
12
|
+
/** The store's IANA zone, for the cutoff (default: the shopper's clock). */
|
|
13
|
+
timeZone?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DeliveryEstimate {
|
|
17
|
+
/** ISO dates (YYYY-MM-DD). */
|
|
18
|
+
ordered: string;
|
|
19
|
+
start: string;
|
|
20
|
+
ships: [string, string];
|
|
21
|
+
arrives: [string, string];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export declare function parseDayRange(value: unknown): [number, number] | null;
|
|
25
|
+
export declare function storeHour(now: Date, timeZone?: string): number;
|
|
26
|
+
export declare function deliveryEstimate(now: Date, spec: DeliverySpec): DeliveryEstimate | null;
|
|
27
|
+
export declare function formatDeliveryRange(from: string, to: string | undefined, locale?: string): string;
|
|
28
|
+
export declare function mountDeliveryEstimate(env: { document: Document; mark(el: Element, key: string): boolean }): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const b=864e5;function f(e){if(typeof e=="number")return Number.isFinite(e)&&e>=0?[Math.floor(e),Math.floor(e)]:null;const t=/^\s*(\d{1,3})\s*(?:[-–—]\s*(\d{1,3}))?\s*$/.exec(String(e??""));if(!t)return null;const i=Number(t[1]),n=t[2]===void 0?i:Number(t[2]);return n>=i?[i,n]:null}const g=e=>Date.UTC(e.getFullYear(),e.getMonth(),e.getDate()),y=e=>{const t=new Date(e).getUTCDay();return t===0||t===6},l=e=>new Date(e).toISOString().slice(0,10);function c(e,t,i){let n=e,r=t;for(;r>0;)n+=864e5,(!i||!y(n))&&(r-=1);return n}function h(e,t){if(t)try{const i=new Intl.DateTimeFormat("en-US",{timeZone:t,hour:"numeric",hourCycle:"h23"}).formatToParts(e).find(n=>n.type==="hour");if(i)return Number(i.value)%24}catch{}return e.getHours()}function D(e,t){const i=f(t.production),n=f(t.shipping);if(!i||!n||!(e instanceof Date)||Number.isNaN(e.getTime()))return null;const r=t.businessDays!==!1,a=g(e);let o=a;const m=Number(t.cutoffHour);for(t.cutoffHour!==void 0&&t.cutoffHour!==null&&t.cutoffHour!==""&&Number.isInteger(m)&&m>=0&&m<=23&&h(e,t.timeZone)>=m&&(o+=864e5);r&&y(o);)o+=864e5;const s=[c(o,i[0],r),c(o,i[1],r)],u=[c(s[0],n[0],r),c(s[1],n[1],r)];return{ordered:l(a),start:l(o),ships:s.map(l),arrives:u.map(l)}}function p(e,t,i){const n=a=>new Date(`${a}T00:00:00Z`);let r;try{r=new Intl.DateTimeFormat(i||void 0,{month:"short",day:"numeric",timeZone:"UTC"})}catch{r=new Intl.DateTimeFormat(void 0,{month:"short",day:"numeric",timeZone:"UTC"})}return!t||t===e?r.format(n(e)):typeof r.formatRange=="function"?r.formatRange(n(e),n(t)):`${r.format(n(e))} \u2013 ${r.format(n(t))}`}const d=(e,t)=>(e.getAttribute(t)||"").trim();function v(e){const{document:t,mark:i}=e;let n=0;return t.querySelectorAll("[data-gem-delivery-estimate]").forEach(r=>{if(!i(r,"delivery-estimate"))return;const a=D(new Date,{production:d(r,"data-gem-delivery-production"),shipping:d(r,"data-gem-delivery-shipping"),businessDays:d(r,"data-gem-delivery-business-days")!=="false",cutoffHour:d(r,"data-gem-delivery-cutoff-hour"),timeZone:d(r,"data-gem-delivery-timezone")});if(!a)return;const o=d(r,"data-gem-delivery-locale")||t.documentElement&&t.documentElement.lang||void 0,m={ordered:[a.ordered,a.ordered],ships:a.ships,arrives:a.arrives};r.querySelectorAll("[data-gem-delivery-date]").forEach(s=>{const u=m[d(s,"data-gem-delivery-date")];u&&(s.textContent=p(u[0],u[1],o),s.setAttribute("data-gem-delivery-from",u[0]),s.setAttribute("data-gem-delivery-to",u[1]))}),r.setAttribute("data-gem-delivery-state","local"),n+=1}),n}export{D as deliveryEstimate,p as formatDeliveryRange,v as mountDeliveryEstimate,f as parseDayRange,h as storeHour};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Types for @gemboss/gem-framework/pod/personalize (personalize.js, copied minified into dist/pod/).
|
|
2
|
+
import type { PersonalizeRead } from "./core.js";
|
|
3
|
+
|
|
4
|
+
export interface PersonalizeWhen {
|
|
5
|
+
name: string;
|
|
6
|
+
op: "set" | "unset" | "is" | "not";
|
|
7
|
+
values: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** The plugin's environment, as the chunk body hands it over (plugins/personalize.js). */
|
|
11
|
+
export interface PodEnv {
|
|
12
|
+
window: Window;
|
|
13
|
+
document: Document;
|
|
14
|
+
on(el: EventTarget, type: string, fn: (event: any) => void, options?: boolean | AddEventListenerOptions): void;
|
|
15
|
+
mark(el: Element, key: string): boolean;
|
|
16
|
+
api?: { emit(event: string, detail?: Record<string, unknown>): void; uploadEndpoint?: string };
|
|
17
|
+
css?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export declare const PERSONALIZE_MESSAGES: Readonly<Record<"required" | "type" | "size" | "failed" | "uploading" | "unavailable", string>>;
|
|
21
|
+
export declare function parsePersonalizeWhen(text: unknown): PersonalizeWhen | null;
|
|
22
|
+
export declare function personalizeWhenHolds(cond: PersonalizeWhen, values: Record<string, PersonalizeRead>): boolean;
|
|
23
|
+
export declare function fileAccepted(file: { type?: string; name?: string }, accept: string | null | undefined): boolean;
|
|
24
|
+
/** Binds every [data-gem-personalize] field in env.document; returns how many it bound. */
|
|
25
|
+
export declare function mountPersonalize(env: PodEnv, onUnmount: (cleanup: () => void) => void): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{FIELD as j,adoptCss as ce,controlsOf as z,fieldName as Z,fieldType as K,readField as T,readScope as ue,scopeOf as A,uploads as L}from"./core.js";const pe={required:"Fill this in to add to cart.",type:"This file type is not accepted.",size:"This file is over {mb} MB.",failed:"The upload failed. Try again.",uploading:"Wait for the upload to finish.",unavailable:"Photo upload is not available."},de=/^\s*(!)?\s*([^!=|]+?)\s*(?:(!=|==|=)\s*(.+?))?\s*$/;function me(l){if(typeof l!="string"||!l.trim())return null;const g=de.exec(l);if(!g)return null;const[,s,u,h,b]=g;if(!h)return{name:u.trim(),op:s?"unset":"set",values:[]};const C=b.split("|").map(y=>y.trim().toLowerCase()).filter(Boolean);return s||!C.length?null:{name:u.trim(),op:h==="!="?"not":"is",values:C}}function fe(l,g){const s=Object.keys(g).find(h=>h.toLowerCase()===l.name.toLowerCase()),u=s?String(g[s].value||"").trim().toLowerCase():"";return l.op==="set"?u!=="":l.op==="unset"?u==="":l.op==="is"?l.values.includes(u):!l.values.includes(u)}function ge(l,g){const s=String(g||"").split(",").map(b=>b.trim().toLowerCase()).filter(Boolean);if(!s.length)return!0;const u=String(l.type||"").toLowerCase(),h=String(l.name||"").toLowerCase();return s.some(b=>b.startsWith(".")?h.endsWith(b):b.endsWith("/*")?u.startsWith(b.slice(0,-1)):u===b)}const p=(l,g)=>l&&l.getAttribute?l.getAttribute(g):null,P=l=>String(l).split("/").pop(),he='[data-gem-element="product-related"], [data-gem-element="product-list"]';function ye(l,g){const{window:s,document:u,on:h,mark:b,api:C}=l,y=new Set,I=new Set,q=new Set;let U=!1,M=!0;const k=e=>[...y].filter(t=>t.isConnected&&A(t)===e),Q=e=>p(e,"data-gem-personalize-required")==="true"||z(e).some(t=>t.required),Y=(e,t,a={})=>(p(e,`data-gem-personalize-message-${t}`)||p(A(e),`data-gem-personalize-message-${t}`)||pe[t]||"").replace(/\{(\w+)\}/g,(o,r)=>r in a?String(a[r]):""),ee=e=>{let t=e.querySelector("[data-gem-personalize-error]");return t||(t=u.createElement("p"),t.className="gem-el-product-personalization__error",t.setAttribute("data-gem-personalize-error",""),t.setAttribute("role","alert"),e.appendChild(t)),t.id||(t.id=`gem-personalize-error-${Math.random().toString(36).slice(2,9)}`),t},W=(e,t,a)=>{const n=ee(e);n.textContent=Y(e,t,a),n.hidden=!1,e.setAttribute("data-gem-personalize-invalid",t),z(e).forEach(o=>{o.setAttribute("aria-invalid","true");const r=(p(o,"aria-describedby")||"").split(/\s+/).filter(Boolean);r.includes(n.id)||o.setAttribute("aria-describedby",[...r,n.id].join(" "))})},w=e=>{if(!e.hasAttribute("data-gem-personalize-invalid"))return;e.removeAttribute("data-gem-personalize-invalid");const t=e.querySelector("[data-gem-personalize-error]");t&&(t.textContent="",t.hidden=!0),z(e).forEach(a=>a.removeAttribute("aria-invalid"))},x=e=>{const{name:t,value:a,type:n,color:o,preview:r}=T(e);e.dispatchEvent(new s.CustomEvent("gem:personalize:change",{bubbles:!0,detail:{name:t,value:a,type:n,color:o,preview:r}}))},_=e=>{const t=e.querySelector("[data-gem-personalize-count]"),a=z(e).find(o=>o.tagName==="TEXTAREA"||o.type==="text"||o.type===""),n=Number(p(e,"data-gem-personalize-max"))||(a&&a.maxLength>0?a.maxLength:0);!t||!a||!n||(t.textContent=`${a.value.length}/${n}`,a.value.length>=n?t.setAttribute("data-gem-personalize-full",""):t.removeAttribute("data-gem-personalize-full"))},R=e=>{const t=k(e).filter(a=>p(a,"data-gem-personalize-when"));for(let a=0;a<4;a+=1){const n=ue(e);let o=!1;for(const r of t){if(p(r,"data-gem-personalize-state")==="unavailable")continue;const c=me(p(r,"data-gem-personalize-when"));if(!c){r.setAttribute("data-gem-personalize-when-invalid","");continue}const i=fe(c,n);r.hidden!==!i&&(r.hidden=!i,z(r).forEach(f=>{f.disabled=!i}),i||w(r),o=!0)}if(!o)break}},te=e=>{if(!b(e,"personalize"))return!1;y.add(e),!U&&l.css&&(ce(e,"personalize","--gem-personalize-css",l.css),U=!0);const t=Number(p(e,"data-gem-personalize-max"));return z(e).forEach(a=>{t>0&&(a.tagName==="TEXTAREA"||a.type==="text")&&!(a.maxLength>0)&&(a.maxLength=t)}),_(e),K(e)==="upload"&&!B(e)&&N(e),!0},$=()=>{const e=Array.prototype.filter.call(u.querySelectorAll(j),te);return new Set(e.map(A)).forEach(R),e.length},O=e=>{const t=L.get(e);t&&t.preview&&I.delete(t.preview)&&s.URL&&s.URL.revokeObjectURL&&s.URL.revokeObjectURL(t.preview),L.delete(e),e.removeAttribute("data-gem-personalize-state");const a=e.querySelector("[data-gem-personalize-thumb]");a&&(a.removeAttribute("src"),a.hidden=!0)},B=e=>{const t=s.GemTheme||{},a=p(e,"data-gem-personalize-endpoint")||p(A(e),"data-gem-personalize-endpoint")||C&&C.uploadEndpoint||t.uploadEndpoint;return typeof a=="string"&&a.trim()?a.trim():""},N=e=>{O(e),w(e),e.setAttribute("data-gem-personalize-state","unavailable"),e.hidden=!0,z(e).forEach(t=>{t.disabled=!0}),x(e)},F=e=>e&&typeof e.json=="function"?e.json().catch(()=>({})):Promise.resolve({}),E=(e,t={})=>Object.assign(new Error(e),{reason:e,...t}),ae=(e,t,a,n,o)=>{const r=(c,i)=>s.fetch(c,{...i,...o?{signal:o}:{}});return r(e,{method:"POST",credentials:"same-origin",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({type:a.type,size:a.size,max_mb:n})}).then(c=>F(c).then(i=>{if(c.status===404)throw E("unavailable");if(c.status===415)throw E("type");if(c.status===413)throw E("size",{mb:Number(i.max_mb)||n});if(!c.ok)throw E("failed");const f=i&&i.upload;if(f&&typeof f.url=="string"){const d=f.headers&&typeof f.headers=="object"?f.headers:{};return r(f.url,{method:typeof f.method=="string"?f.method:"PUT",headers:d,body:a,credentials:"omit"}).then(m=>{if(!m||!m.ok)throw E("failed");return i})}const S=new s.FormData;return S.append("file",a,a.name),S.append("name",Z(t)),r(e,{method:"POST",body:S,credentials:"same-origin"}).then(d=>{if(!d||!d.ok)throw E(d&&d.status===404?"unavailable":"failed");return F(d)})}))},re=(e,t)=>{O(e),w(e);const a=t.files&&t.files[0];if(!a){x(e);return}const n=p(e,"data-gem-personalize-accept")||p(t,"accept")||"image/*",o=Number(p(e,"data-gem-personalize-max-mb"))||10,r=B(e),c=(m,v={mb:o})=>{t.value="",W(e,m,v),x(e)};if(!ge(a,n))return c("type");if(a.size>o*1048576)return c("size");if(!r||typeof s.fetch!="function")return N(e);const i=s.URL&&s.URL.createObjectURL?s.URL.createObjectURL(a):"";i&&I.add(i);const f={uploading:!0,preview:i,url:"",key:""};L.set(e,f),e.setAttribute("data-gem-personalize-state","uploading");const S=e.querySelector("[data-gem-personalize-thumb]");S&&i&&(S.src=i,S.hidden=!1),x(e);const d=s.AbortController?new s.AbortController:null;d&&q.add(d),ae(r,e,a,o,d&&d.signal).then(m=>{const v=m&&typeof m.url=="string"&&/^https?:\/\//i.test(m.url)?m.url:"";if(!v)throw E("failed");L.get(e)===f&&(Object.assign(f,{uploading:!1,url:v,key:typeof m.key=="string"?m.key:""}),e.setAttribute("data-gem-personalize-state","done"),w(e),x(e))}).catch(m=>{if(L.get(e)!==f||d&&d.signal.aborted)return;const v=m&&m.reason;if(v==="unavailable")return N(e);O(e),c(v==="type"||v==="size"?v:"failed",{mb:m&&m.mb||o})}).finally(()=>{d&&q.delete(d)})},D=e=>{const t=e.target&&e.target.closest?e.target.closest(j):null;if(!t||!y.has(t))return;const a=K(t),n=a==="text"||a==="textarea";if(!(e.type==="input"?!n:n)){if(a==="upload"){e.target.type==="file"&&re(t,e.target);return}_(t),T(t).value&&w(t),R(A(t)),x(t)}},ne=e=>k(e).filter(t=>!t.hidden).flatMap(t=>{const a=L.get(t);return a&&a.uploading?[[t,"uploading"]]:Q(t)&&!T(t).value?[[t,"required"]]:[]}),G=e=>{R(e);const t=ne(e);if(k(e).forEach(o=>{t.some(([r])=>r===o)||w(o)}),!t.length)return!0;t.forEach(([o,r])=>W(o,r));const[a]=t[0],n=z(a)[0];return n&&n.focus&&n.focus({preventScroll:!0}),a.scrollIntoView&&a.scrollIntoView({block:"center"}),e.dispatchEvent(new s.CustomEvent("gem:personalize:blocked",{bubbles:!0,detail:{names:t.map(([o])=>Z(o)),reasons:t.map(([,o])=>o)}})),!1},H=e=>k(e).filter(t=>!t.hidden).map(T).filter(t=>t.name&&t.value).flatMap(t=>[{key:t.name,value:t.value},...t.type==="upload"&&t.key?[{key:`_${t.name}_upload`,value:t.key}]:[]]),V=(e,t)=>[...Array.isArray(e)?e.filter(a=>!t.some(n=>a&&n.key===a.key)):[],...t],X=e=>[e,...e.querySelectorAll("[data-gem-variant-id]")].filter(t=>t===e||A(t)===e&&!t.closest(he)).map(t=>p(t,"data-gem-variant-id")).filter(Boolean).map(P),oe=e=>{const t=e.target;if(t&&t.nodeType===1){const r=A(t);if(k(r).length)return r}const a=e.detail||{},n=[a.variantId,...Array.isArray(a.lines)?a.lines.map(r=>r&&r.variantId):[]].filter(Boolean).map(P);return n.length&&[...new Set([...y].filter(r=>r.isConnected).map(A))].find(r=>X(r).some(c=>n.includes(c)))||null},J=e=>{const t=oe(e);if(!t)return;const a=e.detail&&typeof e.detail=="object"?e.detail:null;if(!G(t)){e.stopImmediatePropagation(),e.cancelable&&e.preventDefault(),s.setTimeout(()=>s.dispatchEvent(new s.CustomEvent("gem:cart:add-failed",{detail:{variantId:a&&a.variantId,reason:"personalization"}})),0);return}const n=H(t);if(!a||!n.length)return;if(e.type==="gem:cart:add"){a.properties=V(a.properties,n);return}const o=Array.isArray(a.lines)?a.lines.filter(i=>i&&typeof i=="object"):[],r=X(t),c=o.filter(i=>r.includes(P(i.variantId)));(c.length?c:o).forEach(i=>{i.properties=V(i.properties,n)})},se=e=>[...new Set([...y].filter(t=>t.isConnected).map(A))].filter(t=>t.contains(e)).sort((t,a)=>t.contains(a)?1:-1)[0]||null,ie=e=>{const t=e.target;if(!t||t.tagName!=="FORM")return;const a=se(t);if(a){if(!G(a)){e.preventDefault(),e.stopImmediatePropagation();return}t.hasAttribute("action")&&(t.querySelectorAll("[data-gem-personalize-hidden]").forEach(n=>n.remove()),H(a).forEach(({key:n,value:o})=>{const r=u.createElement("input");Object.assign(r,{type:"hidden",name:`properties[${n}]`,value:o}),r.setAttribute("data-gem-personalize-hidden",""),t.appendChild(r)}))}},le=$();if(h(u,"input",D),h(u,"change",D),h(u,"submit",ie,!0),h(s,"gem:cart:add",J,!0),h(s,"gem:cart:add-many",J,!0),typeof s.MutationObserver=="function"){let e=!1;const t=new s.MutationObserver(a=>{e||!a.some(n=>Array.prototype.some.call(n.addedNodes,o=>o.nodeType===1&&(o.matches(j)||o.querySelector(j))))||(e=!0,Promise.resolve().then(()=>{e=!1,M&&$()}))});t.observe(u.documentElement,{childList:!0,subtree:!0}),g(()=>t.disconnect())}return g(()=>{M=!1,q.forEach(e=>e.abort()),y.forEach(e=>{e.hidden&&(p(e,"data-gem-personalize-when")||p(e,"data-gem-personalize-state")==="unavailable")&&(e.hidden=!1,z(e).forEach(t=>{t.disabled=!1})),O(e),w(e)}),y.clear()}),le}export{pe as PERSONALIZE_MESSAGES,ge as fileAccepted,ye as mountPersonalize,me as parsePersonalizeWhen,fe as personalizeWhenHolds};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Types for @gemboss/gem-framework/pod/print-preview (print-preview.js, copied minified into dist/pod/).
|
|
2
|
+
import type { PersonalizeRead } from "./core.js";
|
|
3
|
+
import type { PodEnv } from "./personalize.js";
|
|
4
|
+
|
|
5
|
+
/** What a preview draws for these field values (each "" when there is nothing to draw). */
|
|
6
|
+
export declare function printPreviewContent(root: Element, values: Record<string, PersonalizeRead>): { text: string; photo: string; color: string };
|
|
7
|
+
/** Binds every [data-gem-print-preview] in env.document; returns how many it bound. */
|
|
8
|
+
export declare function mountPrintPreview(env: PodEnv, onUnmount: (cleanup: () => void) => void): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{adoptCss as y,hexColor as h,readScope as b,scopeOf as x}from"./core.js";const A="[data-gem-print-preview]",v=(i,p)=>i&&i.getAttribute&&i.getAttribute(p)||"";function C(i,p){const c=n=>{const u=n&&Object.keys(p).find(e=>e.toLowerCase()===n.trim().toLowerCase());return u?p[u]:null},l=c(v(i,"data-gem-print-preview-text")),m=c(v(i,"data-gem-print-preview-photo")),f=v(i,"data-gem-print-preview-color").trim(),a=/^from:/i.test(f)?c(f.slice(5)):null,t=m?m.preview||m.value:"";return{text:l?String(l.value||""):"",photo:/^(?:https?:|blob:|data:image\/)/i.test(t)?t:"",color:a?a.color||h(a.value):h(f)}}function P(i,p){const{window:c,document:l,on:m,mark:f}=i;let a=0;return l.querySelectorAll(A).forEach(t=>{if(!f(t,"print-preview"))return;a+=1,i.css&&y(t,"print-preview","--gem-print-preview-css",i.css);const n=t.querySelector("[data-gem-print-preview-area]");if(!n)return;const u=x(t);let e=null,r=null;const w=()=>{if(!e)return;e.style.transform="";const o=n.clientWidth,s=e.scrollWidth;o>0&&s>o&&(e.style.transform=`scale(${Math.max(.3,o/s).toFixed(3)})`)},d=()=>{const{text:o,photo:s,color:g}=C(t,b(u));s?(r||(r=l.createElement("img"),r.className="gem-el-product-print-preview__photo",r.alt="",n.prepend(r)),r.getAttribute("src")!==s&&(r.src=s)):r&&(r.remove(),r=null),o?(e||(e=l.createElement("p"),e.className="gem-el-product-print-preview__text",n.append(e)),e.textContent=o,w()):e&&(e.remove(),e=null),g?t.style.setProperty("--gem-print-preview-color",g):t.style.removeProperty("--gem-print-preview-color"),o||s?t.setAttribute("data-gem-print-preview-state","drawn"):t.removeAttribute("data-gem-print-preview-state")};m(u,"gem:personalize:change",d),typeof c.ResizeObserver=="function"&&new c.ResizeObserver(w).observe(n),d(),p(()=>{r&&r.remove(),e&&e.remove(),t.removeAttribute("data-gem-print-preview-state"),t.style.removeProperty("--gem-print-preview-color")})}),a}export{P as mountPrintPreview,C as printPreviewContent};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
.gem-el-product-personalization { display: grid; gap: var(--gem-space-2xs); min-width: 0; }
|
|
2
|
+
.gem-el-product-personalization[hidden] { display: none; }
|
|
3
|
+
.gem-el-product-personalization__label { display: grid; gap: var(--gem-space-2xs); }
|
|
4
|
+
.gem-el-product-personalization__title { font-size: var(--gem-text-sm); font-weight: 600; }
|
|
5
|
+
.gem-el-product-personalization__required { color: var(--gem-color-danger); }
|
|
6
|
+
.gem-el-product-personalization__input { box-sizing: border-box; width: 100%; min-height: var(--gem-tap-min); padding: var(--gem-space-xs) var(--gem-space-sm); font: inherit; color: var(--gem-color-text); background: var(--gem-color-background); border: var(--gem-stroke, 1px) solid var(--gem-color-border); border-radius: var(--gem-radius-md); }
|
|
7
|
+
textarea.gem-el-product-personalization__input { min-height: calc(var(--gem-tap-min) * 2); resize: vertical; }
|
|
8
|
+
.gem-el-product-personalization__meta { display: flex; gap: var(--gem-space-xs); align-items: baseline; }
|
|
9
|
+
.gem-el-product-personalization__help { margin: 0; color: var(--gem-color-muted); font-size: var(--gem-text-xs); }
|
|
10
|
+
.gem-el-product-personalization__count { margin-left: auto; color: var(--gem-color-muted); font-size: var(--gem-text-xs); font-variant-numeric: tabular-nums; }
|
|
11
|
+
.gem-el-product-personalization__options { display: flex; flex-wrap: wrap; gap: var(--gem-space-xs); margin: 0; padding: 0; border: 0; min-width: 0; }
|
|
12
|
+
.gem-el-product-personalization__option { position: relative; display: inline-grid; justify-items: center; gap: var(--gem-space-2xs); font-size: var(--gem-text-xs); cursor: pointer; }
|
|
13
|
+
.gem-el-product-personalization__option input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
|
|
14
|
+
.gem-el-product-personalization__swatch { display: block; width: var(--gem-tap-min); height: var(--gem-tap-min); border-radius: var(--gem-radius-pill); background: var(--gem-personalize-swatch, var(--gem-color-surface)); border: var(--gem-stroke, 1px) solid var(--gem-color-border); }
|
|
15
|
+
.gem-el-product-personalization__choice-image { display: block; width: calc(var(--gem-tap-min) * 1.75); aspect-ratio: 1; object-fit: cover; border-radius: var(--gem-radius-md); border: var(--gem-stroke, 1px) solid var(--gem-color-border); }
|
|
16
|
+
.gem-el-product-personalization__option input:checked ~ .gem-el-product-personalization__swatch, .gem-el-product-personalization__option input:checked ~ .gem-el-product-personalization__choice-image { box-shadow: 0 0 0 var(--gem-stroke-thick, 2px) var(--gem-color-background), 0 0 0 calc(var(--gem-stroke-thick, 2px) * 2) var(--gem-color-text); }
|
|
17
|
+
.gem-el-product-personalization__option input:focus-visible ~ .gem-el-product-personalization__swatch, .gem-el-product-personalization__option input:focus-visible ~ .gem-el-product-personalization__choice-image { outline: var(--gem-focus-ring); outline-offset: calc(var(--gem-focus-offset) + var(--gem-stroke-thick, 2px) * 2); }
|
|
18
|
+
.gem-el-product-personalization__checkbox { display: inline-flex; align-items: center; gap: var(--gem-space-xs); min-height: var(--gem-tap-min); cursor: pointer; }
|
|
19
|
+
.gem-el-product-personalization__upload { position: relative; display: grid; justify-items: center; gap: var(--gem-space-2xs); padding: var(--gem-space-md); text-align: center; background: var(--gem-color-surface); border: var(--gem-stroke, 1px) dashed var(--gem-color-border); border-radius: var(--gem-radius-md); cursor: pointer; }
|
|
20
|
+
.gem-el-product-personalization__upload:focus-within { outline: var(--gem-focus-ring); outline-offset: var(--gem-focus-offset); }
|
|
21
|
+
.gem-el-product-personalization__file { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
|
|
22
|
+
.gem-el-product-personalization__thumb { display: block; width: calc(var(--gem-tap-min) * 2); aspect-ratio: 1; object-fit: cover; border-radius: var(--gem-radius-sm); }
|
|
23
|
+
.gem-el-product-personalization__thumb[hidden], .gem-el-product-personalization__thumb:not([src]) { display: none; }
|
|
24
|
+
.gem-el-product-personalization__privacy { margin: 0; color: var(--gem-color-muted); font-size: var(--gem-text-xs); }
|
|
25
|
+
.gem-el-product-personalization__error { margin: 0; color: var(--gem-color-danger); font-size: var(--gem-text-xs); }
|
|
26
|
+
.gem-el-product-personalization__error:empty, .gem-el-product-personalization__error[hidden] { display: none; }
|
|
27
|
+
.gem-el-product-print-preview { position: relative; display: block; overflow: hidden; border-radius: var(--gem-media-radius, var(--gem-radius-md)); }
|
|
28
|
+
.gem-el-product-print-preview__media { display: block; width: 100%; height: auto; }
|
|
29
|
+
.gem-el-product-print-preview__area { position: absolute; left: calc(var(--gem-print-preview-x, 20) * 1%); top: calc(var(--gem-print-preview-y, 35) * 1%); width: calc(var(--gem-print-preview-w, 60) * 1%); height: calc(var(--gem-print-preview-h, 20) * 1%); display: grid; place-items: center; overflow: hidden; pointer-events: none; container-type: size; }
|
|
30
|
+
.gem-el-product-print-preview[data-gem-print-preview-shape="circle"] .gem-el-product-print-preview__area { border-radius: 50%; }
|
|
31
|
+
.gem-el-product-print-preview[data-gem-print-preview-font="body"] { --gem-print-preview-font: var(--gem-font-body); }
|
|
32
|
+
.gem-el-product-print-preview[data-gem-print-preview-font="accent"] { --gem-print-preview-font: var(--gem-font-accent, var(--gem-font-heading)); }
|
|
33
|
+
.gem-el-product-delivery-estimate { display: grid; gap: var(--gem-space-xs); font-size: var(--gem-text-sm); }
|
|
34
|
+
.gem-el-product-delivery-estimate__steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gem-space-sm); margin: 0; padding: 0; list-style: none; }
|
|
35
|
+
.gem-el-product-delivery-estimate__step { position: relative; display: grid; justify-items: center; gap: var(--gem-space-2xs); text-align: center; }
|
|
36
|
+
.gem-el-product-delivery-estimate__step::before { content: ""; width: 0.75em; height: 0.75em; border-radius: var(--gem-radius-pill); background: var(--gem-color-accent); }
|
|
37
|
+
.gem-el-product-delivery-estimate__step + .gem-el-product-delivery-estimate__step::after { content: ""; position: absolute; top: 0.375em; right: calc(50% + 0.75em); width: calc(100% - 1.5em + var(--gem-space-sm)); height: var(--gem-stroke, 1px); background: var(--gem-color-border); }
|
|
38
|
+
.gem-el-product-delivery-estimate__label { color: var(--gem-color-muted); font-size: var(--gem-text-xs); }
|
|
39
|
+
.gem-el-product-delivery-estimate__date { font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
40
|
+
.gem-el-product-delivery-estimate__note { margin: 0; color: var(--gem-color-muted); font-size: var(--gem-text-xs); }
|
|
41
|
+
.gem-el-product-delivery-estimate[data-gem-delivery-style="line"] .gem-el-product-delivery-estimate__steps { display: flex; flex-wrap: wrap; gap: var(--gem-space-2xs) var(--gem-space-sm); }
|
|
42
|
+
.gem-el-product-delivery-estimate[data-gem-delivery-style="line"] .gem-el-product-delivery-estimate__step { display: inline-flex; gap: var(--gem-space-2xs); text-align: start; }
|
|
43
|
+
.gem-el-product-delivery-estimate[data-gem-delivery-style="line"] .gem-el-product-delivery-estimate__step::before, .gem-el-product-delivery-estimate[data-gem-delivery-style="line"] .gem-el-product-delivery-estimate__step::after { content: none; }
|
|
44
|
+
.gem-el-product-design-switcher { display: grid; gap: var(--gem-space-xs); }
|
|
45
|
+
.gem-el-product-design-switcher__list { display: flex; flex-wrap: wrap; gap: var(--gem-space-xs); margin: 0; padding: 0; list-style: none; }
|
|
46
|
+
.gem-el-product-design-switcher__item { display: inline-flex; align-items: center; gap: var(--gem-space-2xs); min-height: var(--gem-tap-min); padding: var(--gem-space-2xs) var(--gem-space-sm); font-size: var(--gem-text-sm); color: var(--gem-color-text); text-decoration: none; background: var(--gem-color-background); border: var(--gem-stroke, 1px) solid var(--gem-color-border); border-radius: var(--gem-radius-pill); }
|
|
47
|
+
.gem-el-product-design-switcher__item:hover { border-color: var(--gem-color-text); }
|
|
48
|
+
.gem-el-product-design-switcher__item:focus-visible { outline: var(--gem-focus-ring); outline-offset: var(--gem-focus-offset); }
|
|
49
|
+
.gem-el-product-design-switcher__item[aria-current="page"] { border-color: var(--gem-color-text); box-shadow: inset 0 0 0 var(--gem-stroke, 1px) var(--gem-color-text); }
|
|
50
|
+
.gem-el-product-design-switcher__price { color: var(--gem-color-muted); font-variant-numeric: tabular-nums; }
|
|
51
|
+
.gem-el-product-design-switcher__image { display: none; }
|
|
52
|
+
.gem-el-product-design-switcher[data-gem-design-switcher-style="cards"] .gem-el-product-design-switcher__list { display: grid; grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr)); }
|
|
53
|
+
.gem-el-product-design-switcher[data-gem-design-switcher-style="cards"] .gem-el-product-design-switcher__item { flex-direction: column; align-items: stretch; padding: var(--gem-space-xs); border-radius: var(--gem-radius-md); }
|
|
54
|
+
.gem-el-product-design-switcher[data-gem-design-switcher-style="cards"] .gem-el-product-design-switcher__image { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: var(--gem-radius-sm); }
|
|
55
|
+
.gem-el-product-design-switcher[data-gem-design-switcher-style="cards"] .gem-el-product-design-switcher__label { font-weight: 600; }
|
package/dist/section-gate.js
CHANGED
|
@@ -2073,6 +2073,144 @@ function gemMotionChunks(legacyQuickView) {
|
|
|
2073
2073
|
};
|
|
2074
2074
|
}
|
|
2075
2075
|
|
|
2076
|
+
// ../../packages/gem-framework/src/pod-engine.ts
|
|
2077
|
+
var P = ".gem-el-product-personalization";
|
|
2078
|
+
var V = ".gem-el-product-print-preview";
|
|
2079
|
+
var D = ".gem-el-product-delivery-estimate";
|
|
2080
|
+
var S = ".gem-el-product-design-switcher";
|
|
2081
|
+
var GEM_POD_BUYBOX_CSS_LINES = [
|
|
2082
|
+
// product-personalization: one field per element. The wrapper is the hook; a field whose condition fails is [hidden].
|
|
2083
|
+
`${P} { display: grid; gap: var(--gem-space-2xs); min-width: 0; }`,
|
|
2084
|
+
`${P}[hidden] { display: none; }`,
|
|
2085
|
+
`${P}__label { display: grid; gap: var(--gem-space-2xs); }`,
|
|
2086
|
+
`${P}__title { font-size: var(--gem-text-sm); font-weight: 600; }`,
|
|
2087
|
+
`${P}__required { color: var(--gem-color-danger); }`,
|
|
2088
|
+
`${P}__input { box-sizing: border-box; width: 100%; min-height: var(--gem-tap-min); padding: var(--gem-space-xs) var(--gem-space-sm); font: inherit; color: var(--gem-color-text); background: var(--gem-color-background); border: var(--gem-stroke, 1px) solid var(--gem-color-border); border-radius: var(--gem-radius-md); }`,
|
|
2089
|
+
`textarea${P}__input { min-height: calc(var(--gem-tap-min) * 2); resize: vertical; }`,
|
|
2090
|
+
`${P}__meta { display: flex; gap: var(--gem-space-xs); align-items: baseline; }`,
|
|
2091
|
+
`${P}__help { margin: 0; color: var(--gem-color-muted); font-size: var(--gem-text-xs); }`,
|
|
2092
|
+
`${P}__count { margin-left: auto; color: var(--gem-color-muted); font-size: var(--gem-text-xs); font-variant-numeric: tabular-nums; }`,
|
|
2093
|
+
// swatch and image-choice: radios inside labels; the radio stays focusable, its picture shows the state.
|
|
2094
|
+
`${P}__options { display: flex; flex-wrap: wrap; gap: var(--gem-space-xs); margin: 0; padding: 0; border: 0; min-width: 0; }`,
|
|
2095
|
+
`${P}__option { position: relative; display: inline-grid; justify-items: center; gap: var(--gem-space-2xs); font-size: var(--gem-text-xs); cursor: pointer; }`,
|
|
2096
|
+
`${P}__option input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }`,
|
|
2097
|
+
`${P}__swatch { display: block; width: var(--gem-tap-min); height: var(--gem-tap-min); border-radius: var(--gem-radius-pill); background: var(--gem-personalize-swatch, var(--gem-color-surface)); border: var(--gem-stroke, 1px) solid var(--gem-color-border); }`,
|
|
2098
|
+
`${P}__choice-image { display: block; width: calc(var(--gem-tap-min) * 1.75); aspect-ratio: 1; object-fit: cover; border-radius: var(--gem-radius-md); border: var(--gem-stroke, 1px) solid var(--gem-color-border); }`,
|
|
2099
|
+
`${P}__option input:checked ~ ${P}__swatch, ${P}__option input:checked ~ ${P}__choice-image { box-shadow: 0 0 0 var(--gem-stroke-thick, 2px) var(--gem-color-background), 0 0 0 calc(var(--gem-stroke-thick, 2px) * 2) var(--gem-color-text); }`,
|
|
2100
|
+
`${P}__option input:focus-visible ~ ${P}__swatch, ${P}__option input:focus-visible ~ ${P}__choice-image { outline: var(--gem-focus-ring); outline-offset: calc(var(--gem-focus-offset) + var(--gem-stroke-thick, 2px) * 2); }`,
|
|
2101
|
+
`${P}__checkbox { display: inline-flex; align-items: center; gap: var(--gem-space-xs); min-height: var(--gem-tap-min); cursor: pointer; }`,
|
|
2102
|
+
// upload: a dashed drop box around a visually hidden (still focusable) file input, a thumbnail once a photo is picked.
|
|
2103
|
+
`${P}__upload { position: relative; display: grid; justify-items: center; gap: var(--gem-space-2xs); padding: var(--gem-space-md); text-align: center; background: var(--gem-color-surface); border: var(--gem-stroke, 1px) dashed var(--gem-color-border); border-radius: var(--gem-radius-md); cursor: pointer; }`,
|
|
2104
|
+
`${P}__upload:focus-within { outline: var(--gem-focus-ring); outline-offset: var(--gem-focus-offset); }`,
|
|
2105
|
+
`${P}__file { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }`,
|
|
2106
|
+
`${P}__thumb { display: block; width: calc(var(--gem-tap-min) * 2); aspect-ratio: 1; object-fit: cover; border-radius: var(--gem-radius-sm); }`,
|
|
2107
|
+
`${P}__thumb[hidden], ${P}__thumb:not([src]) { display: none; }`,
|
|
2108
|
+
`${P}__privacy { margin: 0; color: var(--gem-color-muted); font-size: var(--gem-text-xs); }`,
|
|
2109
|
+
`${P}__error { margin: 0; color: var(--gem-color-danger); font-size: var(--gem-text-xs); }`,
|
|
2110
|
+
`${P}__error:empty, ${P}__error[hidden] { display: none; }`,
|
|
2111
|
+
// product-print-preview: the product's current image, and the print area on it (percent of the image, from the element's
|
|
2112
|
+
// area_* props or the gemboss.print_area metafield). Empty until the shopper types or uploads.
|
|
2113
|
+
`${V} { position: relative; display: block; overflow: hidden; border-radius: var(--gem-media-radius, var(--gem-radius-md)); }`,
|
|
2114
|
+
`${V}__media { display: block; width: 100%; height: auto; }`,
|
|
2115
|
+
`${V}__area { position: absolute; left: calc(var(--gem-print-preview-x, 20) * 1%); top: calc(var(--gem-print-preview-y, 35) * 1%); width: calc(var(--gem-print-preview-w, 60) * 1%); height: calc(var(--gem-print-preview-h, 20) * 1%); display: grid; place-items: center; overflow: hidden; pointer-events: none; container-type: size; }`,
|
|
2116
|
+
`${V}[data-gem-print-preview-shape="circle"] ${V}__area { border-radius: 50%; }`,
|
|
2117
|
+
`${V}[data-gem-print-preview-font="body"] { --gem-print-preview-font: var(--gem-font-body); }`,
|
|
2118
|
+
`${V}[data-gem-print-preview-font="accent"] { --gem-print-preview-font: var(--gem-font-accent, var(--gem-font-heading)); }`,
|
|
2119
|
+
// product-delivery-estimate: three steps on a line (timeline), or one sentence (line). Dates are the server's for its today
|
|
2120
|
+
// until the plugin rewrites them in the shopper's.
|
|
2121
|
+
`${D} { display: grid; gap: var(--gem-space-xs); font-size: var(--gem-text-sm); }`,
|
|
2122
|
+
`${D}__steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gem-space-sm); margin: 0; padding: 0; list-style: none; }`,
|
|
2123
|
+
`${D}__step { position: relative; display: grid; justify-items: center; gap: var(--gem-space-2xs); text-align: center; }`,
|
|
2124
|
+
`${D}__step::before { content: ""; width: 0.75em; height: 0.75em; border-radius: var(--gem-radius-pill); background: var(--gem-color-accent); }`,
|
|
2125
|
+
`${D}__step + ${D}__step::after { content: ""; position: absolute; top: 0.375em; right: calc(50% + 0.75em); width: calc(100% - 1.5em + var(--gem-space-sm)); height: var(--gem-stroke, 1px); background: var(--gem-color-border); }`,
|
|
2126
|
+
`${D}__label { color: var(--gem-color-muted); font-size: var(--gem-text-xs); }`,
|
|
2127
|
+
`${D}__date { font-weight: 600; font-variant-numeric: tabular-nums; }`,
|
|
2128
|
+
`${D}__note { margin: 0; color: var(--gem-color-muted); font-size: var(--gem-text-xs); }`,
|
|
2129
|
+
`${D}[data-gem-delivery-style="line"] ${D}__steps { display: flex; flex-wrap: wrap; gap: var(--gem-space-2xs) var(--gem-space-sm); }`,
|
|
2130
|
+
`${D}[data-gem-delivery-style="line"] ${D}__step { display: inline-flex; gap: var(--gem-space-2xs); text-align: start; }`,
|
|
2131
|
+
`${D}[data-gem-delivery-style="line"] ${D}__step::before, ${D}[data-gem-delivery-style="line"] ${D}__step::after { content: none; }`,
|
|
2132
|
+
// product-design-switcher: one link per product that shares the design (pills, or cards with the sibling's photo).
|
|
2133
|
+
`${S} { display: grid; gap: var(--gem-space-xs); }`,
|
|
2134
|
+
`${S}__list { display: flex; flex-wrap: wrap; gap: var(--gem-space-xs); margin: 0; padding: 0; list-style: none; }`,
|
|
2135
|
+
`${S}__item { display: inline-flex; align-items: center; gap: var(--gem-space-2xs); min-height: var(--gem-tap-min); padding: var(--gem-space-2xs) var(--gem-space-sm); font-size: var(--gem-text-sm); color: var(--gem-color-text); text-decoration: none; background: var(--gem-color-background); border: var(--gem-stroke, 1px) solid var(--gem-color-border); border-radius: var(--gem-radius-pill); }`,
|
|
2136
|
+
`${S}__item:hover { border-color: var(--gem-color-text); }`,
|
|
2137
|
+
`${S}__item:focus-visible { outline: var(--gem-focus-ring); outline-offset: var(--gem-focus-offset); }`,
|
|
2138
|
+
`${S}__item[aria-current="page"] { border-color: var(--gem-color-text); box-shadow: inset 0 0 0 var(--gem-stroke, 1px) var(--gem-color-text); }`,
|
|
2139
|
+
`${S}__price { color: var(--gem-color-muted); font-variant-numeric: tabular-nums; }`,
|
|
2140
|
+
`${S}__image { display: none; }`,
|
|
2141
|
+
`${S}[data-gem-design-switcher-style="cards"] ${S}__list { display: grid; grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr)); }`,
|
|
2142
|
+
`${S}[data-gem-design-switcher-style="cards"] ${S}__item { flex-direction: column; align-items: stretch; padding: var(--gem-space-xs); border-radius: var(--gem-radius-md); }`,
|
|
2143
|
+
`${S}[data-gem-design-switcher-style="cards"] ${S}__image { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: var(--gem-radius-sm); }`,
|
|
2144
|
+
`${S}[data-gem-design-switcher-style="cards"] ${S}__label { font-weight: 600; }`
|
|
2145
|
+
];
|
|
2146
|
+
var GEM_PERSONALIZE_RUNTIME_CSS_LINES = [
|
|
2147
|
+
`${P}[data-gem-personalize-invalid] ${P}__input, ${P}[data-gem-personalize-invalid] ${P}__upload, ${P}[data-gem-personalize-invalid] ${P}__swatch, ${P}[data-gem-personalize-invalid] ${P}__choice-image { border-color: var(--gem-color-danger); }`,
|
|
2148
|
+
`${P}[data-gem-personalize-state="uploading"] ${P}__upload { cursor: progress; opacity: 0.7; }`,
|
|
2149
|
+
`${P}__count[data-gem-personalize-full] { color: var(--gem-color-text); font-weight: 600; }`
|
|
2150
|
+
];
|
|
2151
|
+
var GEM_PRINT_PREVIEW_RUNTIME_CSS_LINES = [
|
|
2152
|
+
// The words: the theme font the element names, the colour it follows, as many lines as it allows, sized by the area's
|
|
2153
|
+
// height (container units); a word wider than the area is scaled down by the plugin with a transform, never re-laid out.
|
|
2154
|
+
`${V}__text { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: var(--gem-print-preview-lines, 2); max-width: 100%; margin: 0; overflow: hidden; overflow-wrap: anywhere; font-family: var(--gem-print-preview-font, var(--gem-font-heading)); font-size: calc(82cqh / var(--gem-print-preview-lines, 2)); line-height: 1.1; color: var(--gem-print-preview-color, var(--gem-color-text)); text-align: center; transform-origin: center; }`,
|
|
2155
|
+
`${V}[data-gem-print-preview-align="left"] ${V}__area { place-items: center start; }`,
|
|
2156
|
+
`${V}[data-gem-print-preview-align="left"] ${V}__text { text-align: start; transform-origin: left center; }`,
|
|
2157
|
+
`${V}[data-gem-print-preview-align="right"] ${V}__area { place-items: center end; }`,
|
|
2158
|
+
`${V}[data-gem-print-preview-align="right"] ${V}__text { text-align: end; transform-origin: right center; }`,
|
|
2159
|
+
`${V}__photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }`
|
|
2160
|
+
];
|
|
2161
|
+
var GEM_POD_BUYBOX_CSS = GEM_POD_BUYBOX_CSS_LINES.join("\n") + "\n";
|
|
2162
|
+
var GEM_PERSONALIZE_RUNTIME_CSS = GEM_PERSONALIZE_RUNTIME_CSS_LINES.join("\n") + "\n";
|
|
2163
|
+
var GEM_PRINT_PREVIEW_RUNTIME_CSS = GEM_PRINT_PREVIEW_RUNTIME_CSS_LINES.join("\n") + "\n";
|
|
2164
|
+
var GEM_POD_CLASSES = [
|
|
2165
|
+
...["", "__label", "__title", "__required", "__input", "__meta", "__help", "__count", "__options", "__option", "__swatch", "__choice-image", "__checkbox", "__upload", "__file", "__thumb", "__privacy", "__error"].map((part) => `gem-el-product-personalization${part}`),
|
|
2166
|
+
...["", "__media", "__area", "__text", "__photo"].map((part) => `gem-el-product-print-preview${part}`),
|
|
2167
|
+
...["", "__steps", "__step", "__label", "__date", "__note"].map((part) => `gem-el-product-delivery-estimate${part}`),
|
|
2168
|
+
...["", "__list", "__item", "__label", "__price", "__image"].map((part) => `gem-el-product-design-switcher${part}`)
|
|
2169
|
+
];
|
|
2170
|
+
var GEM_POD_CSS_VARIABLES = [
|
|
2171
|
+
"--gem-personalize-css",
|
|
2172
|
+
"--gem-personalize-swatch",
|
|
2173
|
+
"--gem-print-preview-css",
|
|
2174
|
+
"--gem-print-preview-x",
|
|
2175
|
+
"--gem-print-preview-y",
|
|
2176
|
+
"--gem-print-preview-w",
|
|
2177
|
+
"--gem-print-preview-h",
|
|
2178
|
+
"--gem-print-preview-lines",
|
|
2179
|
+
"--gem-print-preview-color",
|
|
2180
|
+
"--gem-print-preview-font",
|
|
2181
|
+
// No accent font token in gem-framework yet: a skin may set it, the preview falls back to the heading font.
|
|
2182
|
+
"--gem-font-accent"
|
|
2183
|
+
];
|
|
2184
|
+
var GEM_POD_PLUGINS = [
|
|
2185
|
+
{ key: "personalize", hook: "[data-gem-personalize]", when: "viewport", budget: { gzip: 6144 } },
|
|
2186
|
+
{ key: "print-preview", hook: "[data-gem-print-preview]", when: "interaction", budget: { gzip: 6144 } },
|
|
2187
|
+
{ key: "delivery-estimate", hook: "[data-gem-delivery-estimate]", when: "viewport", budget: { gzip: 6144 } }
|
|
2188
|
+
];
|
|
2189
|
+
var MOUNTS = {
|
|
2190
|
+
personalize: { name: "mountPersonalize", file: "personalize", css: GEM_PERSONALIZE_RUNTIME_CSS },
|
|
2191
|
+
"print-preview": { name: "mountPrintPreview", file: "print-preview", css: GEM_PRINT_PREVIEW_RUNTIME_CSS },
|
|
2192
|
+
"delivery-estimate": { name: "mountDeliveryEstimate", file: "delivery-estimate", css: "" }
|
|
2193
|
+
};
|
|
2194
|
+
function gemPodChunks() {
|
|
2195
|
+
const chunks = {};
|
|
2196
|
+
for (const { key, hook, when } of GEM_POD_PLUGINS) {
|
|
2197
|
+
const { name, file, css } = MOUNTS[key];
|
|
2198
|
+
chunks[key] = {
|
|
2199
|
+
hook,
|
|
2200
|
+
when,
|
|
2201
|
+
imports: [`import { ${name} } from "../pod/${file}.js";`],
|
|
2202
|
+
body: [
|
|
2203
|
+
` registerPlugin('${key}', ({ api }) => {`,
|
|
2204
|
+
" const on = (el, type, fn, opt) => { if (canListen(el)) el.addEventListener(type, fn, opt); };",
|
|
2205
|
+
` const count = ${name}({ window, document, on, mark, api${css ? `, css: ${JSON.stringify(compactCss(css))}` : ""} }, onUnmount);`,
|
|
2206
|
+
` api.emit('gem:${key}:ready', { source: 'gem-runtime', count });`,
|
|
2207
|
+
" });"
|
|
2208
|
+
]
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
return chunks;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2076
2214
|
// ../../packages/gem-framework/src/manifest.ts
|
|
2077
2215
|
var gemFrameworkPrimitiveGroups = [
|
|
2078
2216
|
{
|
|
@@ -2274,6 +2412,13 @@ var gemFrameworkPrimitiveGroups = [
|
|
|
2274
2412
|
group: "motion",
|
|
2275
2413
|
classes: [...GEM_MOTION_CLASSES]
|
|
2276
2414
|
},
|
|
2415
|
+
{
|
|
2416
|
+
// The Boss Theme POD buy box (pod-engine.ts, 0.20.0): the four elements gemsite renders (personalization v2, print
|
|
2417
|
+
// preview, delivery estimate, design switcher) and what their plugins draw. Styled by dist/pod-buybox.css and the
|
|
2418
|
+
// personalize / print-preview chunks' own sheets, never by gem-framework.css.
|
|
2419
|
+
group: "pod",
|
|
2420
|
+
classes: [...GEM_POD_CLASSES]
|
|
2421
|
+
},
|
|
2277
2422
|
{
|
|
2278
2423
|
group: "sections",
|
|
2279
2424
|
classes: [
|
|
@@ -7346,11 +7491,14 @@ var legacyQuickViewLines = [
|
|
|
7346
7491
|
// page's api.quickView.
|
|
7347
7492
|
" if (surface || !api.quickView) api.quickView = { open: (detail = {}) => openSurface({ ...detail }), close };"
|
|
7348
7493
|
];
|
|
7349
|
-
var runtimeChunks =
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7494
|
+
var runtimeChunks = {
|
|
7495
|
+
...gemMotionChunks({
|
|
7496
|
+
hook: '[data-gem-quick-view], [data-gem-action="quick-view"]',
|
|
7497
|
+
lines: legacyQuickViewLines,
|
|
7498
|
+
readyDetail: "count: bound + count, hasSurface: Boolean(surface)"
|
|
7499
|
+
}),
|
|
7500
|
+
...gemPodChunks()
|
|
7501
|
+
};
|
|
7354
7502
|
var promoPopupPluginRegistrationLines = [
|
|
7355
7503
|
" registerPlugin('promo-popup', ({ api }) => {",
|
|
7356
7504
|
// Only the explicit hook opts in. Every promo-popup section carries the archetype class .gem-promo-popup, and
|
|
@@ -7940,7 +8088,10 @@ var runtimePluginSourceLines = [
|
|
|
7940
8088
|
["bundle", bundlePluginRegistrationLines],
|
|
7941
8089
|
["size-guide", sizeGuidePluginRegistrationLines],
|
|
7942
8090
|
["orbit", runtimeChunks.orbit.body],
|
|
7943
|
-
["rail", runtimeChunks.rail.body]
|
|
8091
|
+
["rail", runtimeChunks.rail.body],
|
|
8092
|
+
["personalize", runtimeChunks.personalize.body],
|
|
8093
|
+
["print-preview", runtimeChunks["print-preview"].body],
|
|
8094
|
+
["delivery-estimate", runtimeChunks["delivery-estimate"].body]
|
|
7944
8095
|
];
|
|
7945
8096
|
|
|
7946
8097
|
// ../../packages/gem-framework/src/validation.ts
|
|
@@ -7977,7 +8128,10 @@ var canonicalCssVariableNames = /* @__PURE__ */ new Set([
|
|
|
7977
8128
|
// internals (--gem-orbit-a, --gem-rail-t) included, and sets --gem-orbit-i / -n, the settings and --gem-motion-tint.
|
|
7978
8129
|
...new Set([GEM_ORBIT_BASE_CSS, GEM_RAIL_BASE_CSS, GEM_ORBIT_RUNTIME_CSS, GEM_RAIL_RUNTIME_CSS, GEM_QUICK_VIEW_CSS].join("\n").match(/--gem-[a-z0-9-]+/g) ?? []),
|
|
7979
8130
|
// …and every setting an effect reads from a CSS variable (`css: true`): a section sets it in its stylesheet, per breakpoint.
|
|
7980
|
-
...gemMotionEffectList.flatMap((effect) => Object.entries(effect.settings).filter(([, setting]) => setting.css).map(([name]) => `--gem-${effect.key}-${name}`))
|
|
8131
|
+
...gemMotionEffectList.flatMap((effect) => Object.entries(effect.settings).filter(([, setting]) => setting.css).map(([name]) => `--gem-${effect.key}-${name}`)),
|
|
8132
|
+
// The POD buy box's (pod-engine.ts, 0.20.0): the print area and lines gemsite sets on a preview, the swatch colour of an
|
|
8133
|
+
// option, the chunks' sentinels, and --gem-font-accent, the accent font a skin may set (the preview falls back to heading).
|
|
8134
|
+
...GEM_POD_CSS_VARIABLES
|
|
7981
8135
|
]);
|
|
7982
8136
|
var TOKENIZED_PROPERTIES = [
|
|
7983
8137
|
"background",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/gem-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "gem-framework của GemTheme (CSS, runtime, token, bộ kiểm) cộng đúng bộ kiểm section gemtheme dùng khi duyệt. Build từ gemtheme, không sửa ở đây.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"./popup-shell.css": "./dist/popup-shell.css",
|
|
21
21
|
"./orbit-base.css": "./dist/orbit-base.css",
|
|
22
22
|
"./rail-base.css": "./dist/rail-base.css",
|
|
23
|
+
"./pod-buybox.css": "./dist/pod-buybox.css",
|
|
23
24
|
"./gem-runtime.js": "./dist/gem-runtime.js",
|
|
24
25
|
"./runtime-core": {
|
|
25
26
|
"types": "./dist/runtime-core.d.ts",
|
|
@@ -45,6 +46,14 @@
|
|
|
45
46
|
"types": "./dist/motion/*.d.ts",
|
|
46
47
|
"default": "./dist/motion/*.js"
|
|
47
48
|
},
|
|
49
|
+
"./pod/*.js": {
|
|
50
|
+
"types": "./dist/pod/*.d.ts",
|
|
51
|
+
"default": "./dist/pod/*.js"
|
|
52
|
+
},
|
|
53
|
+
"./pod/*": {
|
|
54
|
+
"types": "./dist/pod/*.d.ts",
|
|
55
|
+
"default": "./dist/pod/*.js"
|
|
56
|
+
},
|
|
48
57
|
"./SOURCE.json": "./dist/SOURCE.json"
|
|
49
58
|
},
|
|
50
59
|
"files": [
|