shopmate-sdk 1.1.0 → 1.1.2

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 CHANGED
@@ -2,13 +2,11 @@
2
2
 
3
3
  Lightweight checkout/cart SDK for browser storefronts.
4
4
 
5
- It supports:
5
+ Use it to:
6
6
 
7
- - **ES module import** (`import`)
8
- - **CommonJS require** (`require`)
9
- - **Plain script tag usage** (no module system)
10
-
11
- ---
7
+ - add products to a local cart from any storefront button
8
+ - open the ShopMate checkout in a full-screen iframe
9
+ - support both code-based and HTML attribute-based integrations
12
10
 
13
11
  ## Installation
14
12
 
@@ -16,25 +14,22 @@ It supports:
16
14
  npm install shopmate-sdk
17
15
  ```
18
16
 
19
- ---
20
-
21
- ## Usage
17
+ ## Quick start
22
18
 
23
- ### 1) ES Module (import)
19
+ ### 1) Import in a JavaScript or TypeScript app
24
20
 
25
21
  ```js
26
22
  import ShopMate from 'shopmate-sdk';
27
23
 
28
24
  const sdk = new ShopMate({
29
25
  orgId: 2,
30
- baseUrl: 'https://shopmate.hoshonto.com',
31
- cartPosition: 'right-bottom'
26
+ cartPosition: 'right-bottom' // required: where to show the floating cart
32
27
  });
33
28
 
34
29
  sdk.extract();
35
30
  ```
36
31
 
37
- ### 2) CommonJS (require)
32
+ ### 2) Use CommonJS
38
33
 
39
34
  ```js
40
35
  const ShopMate = require('shopmate-sdk').default;
@@ -43,38 +38,88 @@ const sdk = new ShopMate({ orgId: 2 });
43
38
  sdk.extract();
44
39
  ```
45
40
 
46
- ### 3) Plain browser script (non-module)
41
+ ### 3) Use a plain script tag
42
+
43
+ ```html
44
+ <script
45
+ src="https://cdn.jsdelivr.net/npm/shopmate-sdk@latest/shopmate-sdk.min.js"
46
+ data-org-id="2"
47
+ data-cart-position="right-bottom"
48
+ ></script>
49
+ ```
50
+
51
+ When loaded via `<script src="...">`, the SDK auto-initializes `window.ShopMateInstance` and wires supported selectors once the page finishes loading.
52
+
53
+ ## Copy-paste storefront example
54
+
55
+ Use this if you want the smallest possible integration:
47
56
 
48
57
  ```html
49
- <script
50
- src="/path/to/shopmate-sdk.min.js"
58
+ <button data-shopmate-product-id="101" data-shopmate-quantity="1">
59
+ Add to cart
60
+ </button>
61
+
62
+ <button data-shopmate-checkout="true">
63
+ Checkout
64
+ </button>
65
+
66
+ <script
67
+ src="https://cdn.jsdelivr.net/npm/shopmate-sdk@latest/shopmate-sdk.min.js"
51
68
  data-org-id="2"
52
- data-base-url="https://shopmate.hoshonto.com"
53
69
  data-cart-position="right-bottom"
54
70
  ></script>
55
- <script>
56
- // Available globally
57
- const sdk = new window.ShopMate({ orgId: 2 });
58
- sdk.extract();
59
- </script>
60
71
  ```
61
72
 
62
- When loaded directly via `<script src="...">`, the SDK auto-initializes a global `window.ShopMateInstance` using data attributes on the script tag.
73
+ If you use the browser bundle directly, the SDK will scan the page automatically after load. If you import it in your app, call `sdk.extract()` after rendering the markup.
63
74
 
64
- **Data attributes (used for auto-init):**
65
- - `data-org-id`: Organization ID (required for auto-init)
66
- - `data-base-url`: Custom base URL (optional)
67
- - `data-cart-position`: Cart position on screen (optional)
75
+ ## How to use
68
76
 
69
- ---
77
+ Create an instance, then call `extract()` on the container you want ShopMate to scan:
78
+
79
+ ```js
80
+ const sdk = new ShopMate({ orgId: 2 });
81
+ sdk.extract();
82
+ ```
83
+
84
+ The SDK watches for these triggers:
85
+
86
+ - `[data-shopmate-product-id]` — adds the item to the cart
87
+ - `[data-shopmate-checkout="true"]` — opens checkout with a loading state
88
+ - `a[href^=".../api/v1/pub/checkout?"]` — opens checkout from a URL-based trigger
89
+
90
+ ## Example markup
91
+
92
+ ### Add to cart button
93
+
94
+ ```html
95
+ <button data-shopmate-product-id="101" data-shopmate-quantity="1">
96
+ Add to cart
97
+ </button>
98
+ ```
99
+
100
+ ### Checkout button
101
+
102
+ ```html
103
+ <button data-shopmate-checkout="true">
104
+ Checkout
105
+ </button>
106
+ ```
107
+
108
+ ### URL-based checkout link
109
+
110
+ ```html
111
+ <a href="https://shopmate.example.com/api/v1/pub/checkout?pid=101,102">
112
+ Checkout now
113
+ </a>
114
+ ```
70
115
 
71
116
  ## Configuration
72
117
 
73
118
  `new ShopMate(config)` accepts:
74
119
 
75
- - `orgId` **(number, required)**: Organization ID.
76
- - `baseUrl` *(string, optional)*: Backend base URL. Default: `https://shopmate.hoshonto.com`.
77
- - `cartPosition` *(optional)*: Floating cart position.
120
+ - `orgId` **(number, required)** organization ID
121
+ - `cartPosition` **(string, required)** floating cart position on screen. **Without this, the cart button will not be visible.**
122
+ - `baseUrl` *(string, optional)* backend base URL. Default: `https://shopmate.hoshonto.com`. Rarely needed unless you're using a custom backend.
78
123
 
79
124
  Allowed `cartPosition` values:
80
125
 
@@ -82,83 +127,101 @@ Allowed `cartPosition` values:
82
127
  - `left-center`, `center`, `right-center`
83
128
  - `left-bottom`, `bottom`, `right-bottom`
84
129
 
85
- ---
86
-
87
130
  ## Public API
88
131
 
89
132
  ### `extract(rootElement?: HTMLElement)`
90
133
 
91
- Scans DOM and wires click handlers:
92
-
93
- - `[data-shopmate-product-id]` → add/update cart items
94
- - `[data-shopmate-checkout="true"]` → open checkout popup
134
+ Scans the DOM and wires click handlers for supported product and checkout elements.
95
135
 
96
136
  ### `addToCart(itemId: number, quantity: number, element?: HTMLElement)`
97
137
 
98
- Adds/updates an item in the local cart and updates floating badge.
138
+ Adds or updates a cart item and refreshes the floating badge.
99
139
 
100
140
  ### `openCheckout(buttonElement?: HTMLElement)`
101
141
 
102
- Opens full-screen checkout iframe.
142
+ Opens the checkout iframe and shows a loading state on the clicked trigger.
103
143
 
104
144
  ### `closeCheckout()`
105
145
 
106
- Closes checkout iframe and restores page scroll.
146
+ Closes the checkout iframe and restores page scroll.
107
147
 
108
148
  ### `createCheckoutIframe()`
109
149
 
110
- Creates and returns the iframe element used for checkout.
111
-
112
- ---
113
-
114
- ## Data attributes supported
115
-
116
- ### Product triggers
117
-
118
- ```html
119
- <button data-shopmate-product-id="101" data-shopmate-quantity="1">Add</button>
120
- ```
121
-
122
- ### Checkout trigger
123
-
124
- ```html
125
- <button data-shopmate-checkout="true">Checkout</button>
126
- ```
127
-
128
- ---
150
+ Creates the iframe used for checkout.
129
151
 
130
152
  ## Build outputs
131
153
 
132
154
  `npm run build` generates:
133
155
 
134
- - `shopmate-sdk.js` (IIFE global bundle)
135
- - `shopmate-sdk.min.js` (IIFE global minified bundle)
136
- - `dist/index.mjs` (ESM)
137
- - `dist/index.min.mjs` (ESM minified)
138
- - `dist/index.cjs` (CJS)
139
- - `dist/index.min.cjs` (CJS minified)
140
- - `dist/index.d.ts` (TypeScript declarations)
141
-
142
- ---
156
+ - `shopmate-sdk.js` browser-friendly global bundle
157
+ - `shopmate-sdk.min.js` minified browser bundle
158
+ - `dist/index.mjs` ESM build
159
+ - `dist/index.min.mjs` — minified ESM build
160
+ - `dist/index.cjs` — CommonJS build
161
+ - `dist/index.min.cjs` minified CommonJS build
162
+ - `dist/index.d.ts` TypeScript declarations
143
163
 
144
164
  ## NPM scripts
145
165
 
146
166
  - `npm run build` — build all artifacts
147
167
  - `npm run watch` — watch mode build
148
- - `npm run typecheck` — TS type-check only
168
+ - `npm run typecheck` — TypeScript type-check only
149
169
  - `npm test` — build + verify ESM/CJS/browser bundle loading
150
170
 
151
- ---
152
-
153
171
  ## Package export map
154
172
 
155
173
  - `shopmate-sdk` → default export (`ShopMate` class)
156
174
  - `shopmate-sdk/min` → minified module build
157
175
  - `shopmate-sdk/browser` → browser bundle path
158
176
 
159
- ---
160
-
161
177
  ## Local demo
162
178
 
163
- Use `examples/storefront-demo.html` after building.
164
- # shopmate-sdk
179
+ Open `examples/storefront-demo.html` after building to see the SDK in action.
180
+
181
+ ## Publishing to npm
182
+
183
+ ### Prerequisites
184
+
185
+ - Ensure you have an npm account with publish rights to the `shopmate-sdk` package
186
+ - Be authenticated: `npm login`
187
+
188
+ ### Steps
189
+
190
+ 1. **Update the version** in `package.json`:
191
+ ```bash
192
+ npm version patch # for bug fixes (1.0.0 → 1.0.1)
193
+ npm version minor # for new features (1.0.0 → 1.1.0)
194
+ npm version major # for breaking changes (1.0.0 → 2.0.0)
195
+ ```
196
+
197
+ 2. **Build the package**:
198
+ ```bash
199
+ npm run build
200
+ ```
201
+
202
+ 3. **Verify the build** by checking the `dist/` and browser bundle files are present.
203
+
204
+ 4. **Publish to npm**:
205
+ ```bash
206
+ npm publish
207
+ ```
208
+
209
+ 5. **Verify the publication**:
210
+ ```bash
211
+ npm view shopmate-sdk
212
+ ```
213
+
214
+ The package will be available on jsDelivr immediately at:
215
+ - `https://cdn.jsdelivr.net/npm/shopmate-sdk@latest/shopmate-sdk.min.js`
216
+ - `https://cdn.jsdelivr.net/npm/shopmate-sdk@<version>/shopmate-sdk.min.js`
217
+
218
+ ### Publishing checklist
219
+
220
+ - [ ] Version number updated and committed
221
+ - [ ] All tests pass (`npm test`)
222
+ - [ ] Build succeeds without warnings (`npm run build`)
223
+ - [ ] README and documentation are up to date
224
+ - [ ] Changelog entry added (if applicable)
225
+ - [ ] Authentication verified (`npm whoami`)
226
+ - [ ] Package published (`npm publish`)
227
+ - [ ] jsDelivr cache updated (automatic after ~10 minutes)
package/dist/index.cjs CHANGED
@@ -1 +1,360 @@
1
- "use strict";var s=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var m=(i,t)=>{for(var e in t)s(i,e,{get:t[e],enumerable:!0})},p=(i,t,e,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of h(t))!f.call(i,n)&&n!==e&&s(i,n,{get:()=>t[n],enumerable:!(o=d(t,n))||o.enumerable});return i};var u=i=>p(s({},"__esModule",{value:!0}),i);var b={};m(b,{default:()=>g});module.exports=u(b);var r=class{constructor(t){this.baseUrl="https://shopmate.hoshonto.com";this.cartPosition="right-bottom";this.iframeShown=!1;this.cart={i:[]};this.orgId=t.orgId,t.baseUrl&&(this.baseUrl=t.baseUrl.replace(/\/$/,"")),t.cartPosition&&(this.cartPosition=t.cartPosition),this.checkoutUrl=`${this.baseUrl}/api/v1/pub/checkout`,this.s()}t(){return document.body||document.documentElement}r(t){if(document.body){t();return}let e=()=>{document.removeEventListener("DOMContentLoaded",e),t()};document.addEventListener("DOMContentLoaded",e)}e(t){let e=t?"hidden":"",o=t?"none":"";document.body&&(document.body.style.overflow=e,document.body.style.touchAction=o),document.documentElement.style.overflow=e,document.documentElement.style.touchAction=o}o(t,e){t&&(t.style.pointerEvents=e?"none":"",t.style.opacity=e?"0.6":"")}n(t,e=1.15,o=260){!t||typeof t.animate!="function"||t.animate([{transform:"scale(1)"},{transform:`scale(${e})`},{transform:"scale(1)"}],{duration:o,easing:"ease-in-out"})}l(){let t="20px",e={position:"fixed",top:"auto",right:"auto",bottom:"auto",left:"auto",transform:"none"},o=this.cartPosition,n=o.indexOf("top")!==-1,a=o.indexOf("bottom")!==-1,l=o.indexOf("left")!==-1,c=o.indexOf("right")!==-1;return n?e.top=t:a?e.bottom=t:(e.top="50%",e.transform="translateY(-50%)"),l?e.left=t:c?e.right=t:(e.left="50%",e.transform=e.transform==="none"?"translateX(-50%)":"translate(-50%, -50%)"),e}d(t){if(this.floatingCartButton)return;let e=t||this.t();if(!e)return;let o=document.createElement("button");o.type="button";let n={width:"60px",height:"60px",borderRadius:"50%",border:"none",background:"#1d4ed8",color:"#ffffff",fontSize:"24px",display:"flex",alignItems:"center",justifyContent:"center",boxShadow:"0 10px 25px rgba(0,0,0,0.2)",cursor:"pointer",zIndex:"2147483646",transition:"transform 0.2s ease, opacity 0.2s ease"};Object.assign(o.style,n,this.l()),o.textContent="🛒";let a=document.createElement("span");Object.assign(a.style,{position:"absolute",top:"0",right:"0",minWidth:"20px",height:"20px",borderRadius:"10px",background:"#ef4444",color:"white",fontSize:"11px",fontWeight:"bold",display:"none",alignItems:"center",justifyContent:"center",border:"2px solid #ffffff",padding:"0 4px"}),o.appendChild(a),o.addEventListener("click",()=>this.openCheckout(o)),e.appendChild(o),this.floatingCartButton=o,this.floatingCartBadge=a}a(){if(!this.floatingCartBadge)return;let t=this.cart.i.reduce((e,o)=>e+o.q,0);this.floatingCartBadge.style.display=t>0?"flex":"none",this.floatingCartBadge.textContent=t>99?"99+":String(t)}s(){window.addEventListener("message",t=>{var e,o;try{if(console.log("Received message from iframe:",t.data),t.origin!==new URL(this.baseUrl).origin)return;console.log("Message origin verified:",t.origin),(((e=t.data)==null?void 0:e.type)==="closeCheckout"||((o=t.data)==null?void 0:o.type)==="shopmate:close")&&(console.log("Received closeCheckout message from iframe"),this.closeCheckout())}catch(n){}})}addToCart(t,e,o){let n=this.cart.i.find(a=>a.i===t);n?n.q=e:this.cart.i.push({i:t,q:e}),o&&(this.n(o,1.15,300),this.h(o)),this.a()}h(t){if(!this.floatingCartButton)return;let e=t.getBoundingClientRect(),o=this.floatingCartButton.getBoundingClientRect(),n=document.createElement("div");Object.assign(n.style,{position:"fixed",left:`${e.left+e.width/2}px`,top:`${e.top+e.height/2}px`,width:"12px",height:"12px",borderRadius:"50%",background:"#1d4ed8",zIndex:"2147483645",pointerEvents:"none",transition:"all 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275)"}),this.t().appendChild(n),requestAnimationFrame(()=>{n.style.transform=`translate(${o.left-e.left}px, ${o.top-e.top}px) scale(0.5)`,n.style.opacity="0"}),setTimeout(()=>{n.remove(),this.n(this.floatingCartButton,1.2,300)},600)}openCheckout(t){this.o(t,!0),this.iframeElement||(this.iframeElement=this.createCheckoutIframe(),this.t().appendChild(this.iframeElement));let e=new URLSearchParams({orgId:this.orgId.toString(),c:JSON.stringify(this.cart),next:window.location.href});this.iframeElement.src=`${this.checkoutUrl}?${e.toString()}`,this.iframeElement.onload=()=>{this.iframeElement&&(this.iframeElement.style.display="block",this.iframeShown=!0,this.e(!0),this.o(t,!1))}}createCheckoutIframe(){let t=document.createElement("iframe");return t.setAttribute("sandbox","allow-scripts allow-same-origin allow-popups allow-forms"),t.setAttribute("allow","payment *; fullscreen *;"),Object.assign(t.style,{width:"100vw",height:"100dvh",position:"fixed",top:"0",left:"0",right:"0",bottom:"0",border:"0",zIndex:"2147483647",background:"#ffffff",display:"none"}),t}closeCheckout(){this.iframeElement&&(this.iframeElement.style.display="none",this.iframeShown=!1,this.e(!1))}extract(t=document.body){t.querySelectorAll("[data-shopmate-product-id]").forEach(e=>{let o=e,n=parseInt(o.getAttribute("data-shopmate-product-id")||"",10);isNaN(n)||o.addEventListener("click",a=>{a.preventDefault(),this.addToCart(n,parseInt(o.getAttribute("data-shopmate-quantity")||"1",10),o)})}),t.querySelectorAll('[data-shopmate-checkout="true"]').forEach(e=>{e.addEventListener("click",o=>{o.preventDefault(),this.openCheckout(e)})}),this.r(()=>{this.d(t),this.a()})}};try{Object.defineProperty(r,"name",{value:"ShopMate"})}catch(i){}if(typeof window!="undefined"&&typeof document!="undefined"){window.ShopMate=r;let i=document.currentScript;if(i instanceof HTMLScriptElement&&i.src){let t=new r({orgId:parseInt(i.dataset.orgId||"2",10),baseUrl:i.dataset.baseUrl||void 0,cartPosition:i.dataset.cartPosition||void 0});window.addEventListener("load",()=>{window.ShopMateInstance=t,t.extract()})}}var g=r;
1
+ /* Shopmate SDK */
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ default: () => index_default
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var ShopMate = class {
28
+ constructor(config) {
29
+ this.baseUrl = "https://shopmate.hoshonto.com";
30
+ this.cartPosition = "right-bottom";
31
+ this.iframeShown = false;
32
+ this.cart = { i: [] };
33
+ this.orgId = config.orgId;
34
+ if (config.baseUrl) this.baseUrl = config.baseUrl.replace(/\/$/, "");
35
+ if (config.cartPosition) this.cartPosition = config.cartPosition;
36
+ this.checkoutUrl = `${this.baseUrl}/api/v1/pub/checkout`;
37
+ this._injectGlobalStyles();
38
+ this._setupMessageListener();
39
+ }
40
+ _getMountRoot() {
41
+ return document.body || document.documentElement;
42
+ }
43
+ _runWhenBodyReady(fn) {
44
+ if (document.body) {
45
+ fn();
46
+ return;
47
+ }
48
+ const onReady = () => {
49
+ document.removeEventListener("DOMContentLoaded", onReady);
50
+ fn();
51
+ };
52
+ document.addEventListener("DOMContentLoaded", onReady);
53
+ }
54
+ _injectGlobalStyles() {
55
+ if (document.getElementById("shopmate-global-styles")) return;
56
+ const style = document.createElement("style");
57
+ style.id = "shopmate-global-styles";
58
+ style.innerHTML = `@keyframes sm-spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes sm-soft-pulse{0%,100%{opacity:.65;transform:scale(1)}50%{opacity:1;transform:scale(1.03)}}.sm-loading-spinner{display:none;width:24px;height:24px;border:3px solid rgba(255,255,255,.28);border-radius:50%;border-top-color:#fff;animation:.8s linear infinite sm-spin}.sm-loading-shell{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:24px;color:#fff;font-weight:600;line-height:1}.sm-loading-text{font-size:13px;letter-spacing:.01em;white-space:nowrap}.sm-btn-loading{position:relative!important;overflow:hidden!important}.sm-btn-loading::before{content:'';position:absolute;inset:0;background:linear-gradient(90deg,rgba(255,255,255,.06),rgba(255,255,255,.16),rgba(255,255,255,.06));animation:1.2s ease-in-out infinite sm-soft-pulse;pointer-events:none}.sm-btn-loading .sm-loading-spinner{display:inline-block}.sm-btn-loading .sm-cart-icon{display:none}.sm-btn-loading .sm-badge{display:none!important}.sm-no-scroll{overflow:hidden!important;touch-action:none;-ms-touch-action:none}`;
59
+ document.head.appendChild(style);
60
+ }
61
+ _setScrollLocked(locked) {
62
+ const action = locked ? "add" : "remove";
63
+ document.body.classList[action]("sm-no-scroll");
64
+ document.documentElement.classList[action]("sm-no-scroll");
65
+ }
66
+ _toggleLoading(button, isLoading) {
67
+ if (!button) return;
68
+ if (isLoading) {
69
+ if (!button.dataset.smOriginalHtml) {
70
+ button.dataset.smOriginalHtml = button.innerHTML;
71
+ }
72
+ const shouldShowText = button.clientWidth >= 100 && button.clientHeight >= 44;
73
+ button.innerHTML = `
74
+ <span class="sm-loading-shell" aria-hidden="true">
75
+ <span class="sm-loading-spinner"></span>
76
+ ${shouldShowText ? '<span class="sm-loading-text">Opening…</span>' : ""}
77
+ </span>
78
+ `;
79
+ } else if (button.dataset.smOriginalHtml !== void 0) {
80
+ button.innerHTML = button.dataset.smOriginalHtml;
81
+ delete button.dataset.smOriginalHtml;
82
+ }
83
+ button.style.pointerEvents = isLoading ? "none" : "";
84
+ button.setAttribute("aria-busy", isLoading ? "true" : "false");
85
+ if (isLoading) button.classList.add("sm-btn-loading");
86
+ else button.classList.remove("sm-btn-loading");
87
+ }
88
+ _pop(el, scale = 1.15, duration = 260) {
89
+ if (!el || typeof el.animate !== "function") return;
90
+ el.animate(
91
+ [{ transform: "scale(1)" }, { transform: `scale(${scale})` }, { transform: "scale(1)" }],
92
+ { duration, easing: "ease-in-out" }
93
+ );
94
+ }
95
+ _getPositionStyles() {
96
+ const spacing = "20px";
97
+ const styles = {
98
+ position: "fixed",
99
+ top: "auto",
100
+ right: "auto",
101
+ bottom: "auto",
102
+ left: "auto",
103
+ transform: "none"
104
+ };
105
+ const pos = this.cartPosition;
106
+ if (pos.includes("top")) styles.top = spacing;
107
+ else if (pos.includes("bottom")) styles.bottom = spacing;
108
+ else {
109
+ styles.top = "50%";
110
+ styles.transform = "translateY(-50%)";
111
+ }
112
+ if (pos.includes("left")) styles.left = spacing;
113
+ else if (pos.includes("right")) styles.right = spacing;
114
+ else {
115
+ styles.left = "50%";
116
+ const currentY = styles.transform !== "none" ? "translateY(-50%)" : "";
117
+ styles.transform = `translateX(-50%) ${currentY}`.trim();
118
+ }
119
+ return styles;
120
+ }
121
+ _ensureFloatingCart(rootElement) {
122
+ if (this.floatingCartButton) return;
123
+ const mountRoot = rootElement || this._getMountRoot();
124
+ const button = document.createElement("button");
125
+ button.type = "button";
126
+ Object.assign(button.style, {
127
+ width: "60px",
128
+ height: "60px",
129
+ borderRadius: "50%",
130
+ border: "none",
131
+ background: "#1d4ed8",
132
+ color: "#ffffff",
133
+ fontSize: "24px",
134
+ display: "flex",
135
+ alignItems: "center",
136
+ justifyContent: "center",
137
+ boxShadow: "0 10px 25px rgba(0,0,0,0.2)",
138
+ cursor: "pointer",
139
+ zIndex: "2147483646",
140
+ transition: "transform 0.2s ease"
141
+ }, this._getPositionStyles());
142
+ button.innerHTML = `
143
+ <span class="sm-cart-icon">🛒</span>
144
+ <div class="sm-loading-spinner"></div>
145
+ `;
146
+ const badge = document.createElement("span");
147
+ badge.className = "sm-badge";
148
+ Object.assign(badge.style, {
149
+ position: "absolute",
150
+ top: "0",
151
+ right: "0",
152
+ minWidth: "20px",
153
+ height: "20px",
154
+ borderRadius: "10px",
155
+ background: "#ef4444",
156
+ color: "white",
157
+ fontSize: "11px",
158
+ fontWeight: "bold",
159
+ display: "none",
160
+ alignItems: "center",
161
+ justifyContent: "center",
162
+ border: "2px solid #ffffff",
163
+ padding: "0 4px"
164
+ });
165
+ button.appendChild(badge);
166
+ button.addEventListener("click", () => this.openCheckout(button));
167
+ mountRoot.appendChild(button);
168
+ this.floatingCartButton = button;
169
+ this.floatingCartBadge = badge;
170
+ }
171
+ _updateFloatingCartBadge() {
172
+ if (!this.floatingCartBadge) return;
173
+ const count = this.cart.i.reduce((sum, item) => sum + item.q, 0);
174
+ this.floatingCartBadge.style.display = count > 0 ? "flex" : "none";
175
+ this.floatingCartBadge.textContent = count > 99 ? "99+" : String(count);
176
+ }
177
+ _setupMessageListener() {
178
+ window.addEventListener("message", (event) => {
179
+ var _a, _b;
180
+ try {
181
+ const expectedOrigin = new URL(this.baseUrl).origin;
182
+ if (event.origin !== expectedOrigin) return;
183
+ if (((_a = event.data) == null ? void 0 : _a.type) === "closeCheckout" || ((_b = event.data) == null ? void 0 : _b.type) === "shopmate:close") {
184
+ this.closeCheckout();
185
+ }
186
+ } catch (e) {
187
+ }
188
+ });
189
+ }
190
+ addToCart(itemId, quantity, element) {
191
+ const item = this.cart.i.find((i) => i.i === itemId);
192
+ if (item) item.q = quantity;
193
+ else this.cart.i.push({ i: itemId, q: quantity });
194
+ if (element) {
195
+ this._pop(element, 1.15, 300);
196
+ this._animateFlyToCart(element);
197
+ }
198
+ this._updateFloatingCartBadge();
199
+ }
200
+ _animateFlyToCart(sourceElement) {
201
+ if (!this.floatingCartButton) return;
202
+ const sourceRect = sourceElement.getBoundingClientRect();
203
+ const cartRect = this.floatingCartButton.getBoundingClientRect();
204
+ const dot = document.createElement("div");
205
+ const sourceStyles = getComputedStyle(sourceElement);
206
+ const backgroundColor = sourceStyles.backgroundColor || "#1d4ed8";
207
+ const dotSize = Math.max(10, Math.min(sourceRect.width, sourceRect.height) / 2);
208
+ Object.assign(dot.style, {
209
+ position: "fixed",
210
+ left: `${sourceRect.left + sourceRect.width / 2}px`,
211
+ top: `${sourceRect.top + sourceRect.height / 2}px`,
212
+ width: `${dotSize}px`,
213
+ height: `${dotSize}px`,
214
+ borderRadius: "50%",
215
+ background: backgroundColor,
216
+ zIndex: "2147483645",
217
+ pointerEvents: "none"
218
+ });
219
+ this._getMountRoot().appendChild(dot);
220
+ const duration = 1150;
221
+ dot.animate([
222
+ { transform: "translate(0, 0) scale(1)", opacity: "1" },
223
+ { transform: `translate(${cartRect.left - sourceRect.left}px, ${cartRect.top - sourceRect.top}px) scale(0.5)`, opacity: "0" }
224
+ ], {
225
+ duration,
226
+ easing: "ease-out",
227
+ fill: "forwards"
228
+ });
229
+ setTimeout(() => {
230
+ dot.remove();
231
+ this._pop(this.floatingCartButton, 1.2, 300);
232
+ }, duration);
233
+ }
234
+ openCheckout(buttonElement) {
235
+ this._toggleLoading(buttonElement, true);
236
+ if (!this.iframeElement) {
237
+ this.iframeElement = this.createCheckoutIframe();
238
+ this._getMountRoot().appendChild(this.iframeElement);
239
+ }
240
+ const params = new URLSearchParams({
241
+ orgId: this.orgId.toString(),
242
+ c: JSON.stringify(this.cart),
243
+ next: window.location.href
244
+ });
245
+ this.iframeElement.src = `${this.checkoutUrl}?${params.toString()}`;
246
+ this.iframeElement.onload = () => {
247
+ if (this.iframeElement && this.iframeElement.hasAttribute("src")) {
248
+ this.iframeElement.style.display = "block";
249
+ this.iframeShown = true;
250
+ this._setScrollLocked(true);
251
+ this._toggleLoading(buttonElement, false);
252
+ }
253
+ };
254
+ this.iframeElement.onerror = () => {
255
+ this._toggleLoading(buttonElement, false);
256
+ };
257
+ }
258
+ createCheckoutIframe() {
259
+ const iframe = document.createElement("iframe");
260
+ iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-popups allow-forms");
261
+ iframe.setAttribute("allow", "payment *; fullscreen *;");
262
+ Object.assign(iframe.style, {
263
+ width: "100vw",
264
+ height: "100dvh",
265
+ position: "fixed",
266
+ top: "0",
267
+ left: "0",
268
+ right: "0",
269
+ bottom: "0",
270
+ border: "0",
271
+ zIndex: "2147483647",
272
+ background: "#ffffff",
273
+ display: "none"
274
+ });
275
+ return iframe;
276
+ }
277
+ closeCheckout() {
278
+ if (this.iframeElement) {
279
+ this.iframeElement.style.display = "none";
280
+ this.iframeShown = false;
281
+ this.iframeElement.removeAttribute("src");
282
+ this._setScrollLocked(false);
283
+ }
284
+ }
285
+ extract(rootElement = document.body) {
286
+ rootElement.querySelectorAll("[data-shopmate-product-id]").forEach((el) => {
287
+ const htmlEl = el;
288
+ const productId = parseInt(htmlEl.getAttribute("data-shopmate-product-id") || "", 10);
289
+ if (!isNaN(productId)) {
290
+ htmlEl.addEventListener("click", (e) => {
291
+ e.preventDefault();
292
+ this.addToCart(productId, parseInt(htmlEl.getAttribute("data-shopmate-quantity") || "1", 10), htmlEl);
293
+ });
294
+ }
295
+ });
296
+ const urlPrefix = this.checkoutUrl + "?";
297
+ const targetElementsWithUrlTrigger = rootElement.querySelectorAll(`a[href^="${urlPrefix}"]`);
298
+ targetElementsWithUrlTrigger.forEach((el) => {
299
+ const anchor = el;
300
+ const href = anchor.getAttribute("href") || "";
301
+ if (href.startsWith(urlPrefix)) {
302
+ const url = new URL(href, window.location.origin);
303
+ const productIdsRaw = url.searchParams.get("pid");
304
+ let productIds = [];
305
+ const cartRaw = url.searchParams.get("c");
306
+ let cart = null;
307
+ if (productIdsRaw) {
308
+ productIds = productIdsRaw.split(",").map((id) => parseInt(id, 10)).filter((id) => !isNaN(id));
309
+ } else if (cartRaw) {
310
+ try {
311
+ cart = JSON.parse(cartRaw);
312
+ if (cart && typeof cart === "object" && Array.isArray(cart.i)) {
313
+ cart.i = cart.i.filter((item) => typeof item.i === "number" && typeof item.q === "number");
314
+ } else {
315
+ cart = null;
316
+ }
317
+ } catch (e) {
318
+ console.warn("Invalid cart data in URL:", e);
319
+ cart = null;
320
+ }
321
+ }
322
+ anchor.addEventListener("click", (e) => {
323
+ e.preventDefault();
324
+ if (productIds.length > 0) {
325
+ productIds.forEach((pid) => this.addToCart(pid, 1, anchor));
326
+ } else if (cart) {
327
+ cart.i.forEach((item) => this.addToCart(item.i, item.q, anchor));
328
+ }
329
+ this.openCheckout(anchor);
330
+ });
331
+ }
332
+ });
333
+ rootElement.querySelectorAll('[data-shopmate-checkout="true"]').forEach((el) => {
334
+ el.addEventListener("click", (e) => {
335
+ e.preventDefault();
336
+ this.openCheckout(el);
337
+ });
338
+ });
339
+ this._runWhenBodyReady(() => {
340
+ this._ensureFloatingCart(rootElement);
341
+ this._updateFloatingCartBadge();
342
+ });
343
+ }
344
+ };
345
+ if (typeof window !== "undefined") {
346
+ window.ShopMate = ShopMate;
347
+ const script = document.currentScript;
348
+ if (script && script.src) {
349
+ const instance = new ShopMate({
350
+ orgId: parseInt(script.dataset.orgId || "2", 10),
351
+ baseUrl: script.dataset.baseUrl || void 0,
352
+ cartPosition: script.dataset.cartPosition || void 0
353
+ });
354
+ window.addEventListener("load", () => {
355
+ window.ShopMateInstance = instance;
356
+ instance.extract();
357
+ });
358
+ }
359
+ }
360
+ var index_default = ShopMate;
package/dist/index.d.ts CHANGED
@@ -24,8 +24,9 @@ declare class ShopMate {
24
24
  private _getMountRoot;
25
25
  private _runWhenBodyReady;
26
26
  constructor(config: ShopMateConfig);
27
+ private _injectGlobalStyles;
27
28
  private _setScrollLocked;
28
- private _pulseButton;
29
+ private _toggleLoading;
29
30
  private _pop;
30
31
  private _getPositionStyles;
31
32
  private _ensureFloatingCart;