@coincircuit/checkout 0.4.0 → 0.4.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/dist/react.cjs CHANGED
@@ -69,6 +69,206 @@ function createMessageListener(allowedOrigin, handlers) {
69
69
  return () => window.removeEventListener("message", onMessage);
70
70
  }
71
71
 
72
+ // src/overlay-styles.ts
73
+ var OVERLAY_ID = "coincircuit-checkout-overlay";
74
+ var OVERLAY_STYLES = `
75
+ #${OVERLAY_ID} {
76
+ --cc-surface: #fafafa;
77
+ --cc-foreground: #171717;
78
+ --cc-muted: #666;
79
+ --cc-edge: rgba(255, 255, 255, 0.85);
80
+ --cc-control: rgba(250, 250, 250, 0.88);
81
+ --cc-track: rgba(0, 0, 0, 0.1);
82
+ --cc-accent: oklch(0.52 0.22 255);
83
+ position: fixed;
84
+ inset: 0;
85
+ z-index: 999999;
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ box-sizing: border-box;
90
+ padding: 24px 64px;
91
+ background: rgba(12, 14, 18, 0.42);
92
+ -webkit-backdrop-filter: blur(10px);
93
+ backdrop-filter: blur(10px);
94
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
95
+ color: var(--cc-foreground);
96
+ opacity: 0;
97
+ transition: opacity 200ms ease;
98
+ }
99
+ #${OVERLAY_ID}[data-theme="dark"] {
100
+ --cc-surface: #111216;
101
+ --cc-foreground: #f5f5f5;
102
+ --cc-muted: #aaa;
103
+ --cc-edge: rgba(255, 255, 255, 0.18);
104
+ --cc-control: rgba(28, 29, 33, 0.9);
105
+ --cc-track: rgba(255, 255, 255, 0.14);
106
+ --cc-accent: oklch(0.72 0.29 263.25);
107
+ color-scheme: dark;
108
+ }
109
+ #${OVERLAY_ID}, #${OVERLAY_ID} * {
110
+ box-sizing: border-box;
111
+ }
112
+ #${OVERLAY_ID}.cc-visible {
113
+ opacity: 1;
114
+ }
115
+ #${OVERLAY_ID} .cc-modal {
116
+ position: relative;
117
+ width: 100%;
118
+ max-width: 740px;
119
+ height: min(900px, calc(100vh - 48px));
120
+ height: min(900px, calc(100dvh - 48px));
121
+ border: 1px solid var(--cc-edge);
122
+ border-radius: 30px;
123
+ background: var(--cc-surface);
124
+ box-shadow: 0 32px 100px -24px rgba(0, 0, 0, 0.38);
125
+ transform: translateY(8px) scale(0.99);
126
+ transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
127
+ }
128
+ #${OVERLAY_ID}.cc-visible .cc-modal {
129
+ transform: translateY(0) scale(1);
130
+ }
131
+ #${OVERLAY_ID} .cc-frame {
132
+ position: relative;
133
+ width: 100%;
134
+ height: 100%;
135
+ overflow: hidden;
136
+ border-radius: inherit;
137
+ background: var(--cc-surface);
138
+ }
139
+ #${OVERLAY_ID} .cc-close {
140
+ position: absolute;
141
+ top: 0;
142
+ right: -56px;
143
+ z-index: 2;
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ width: 44px;
148
+ height: 44px;
149
+ margin: 0;
150
+ padding: 0;
151
+ border: 1px solid var(--cc-edge);
152
+ border-radius: 50%;
153
+ background: var(--cc-control);
154
+ -webkit-backdrop-filter: blur(16px);
155
+ backdrop-filter: blur(16px);
156
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
157
+ color: var(--cc-foreground);
158
+ cursor: pointer;
159
+ touch-action: manipulation;
160
+ transition: background-color 160ms ease, transform 160ms ease;
161
+ }
162
+ #${OVERLAY_ID} .cc-close:hover {
163
+ background: var(--cc-surface);
164
+ }
165
+ #${OVERLAY_ID} .cc-close:active {
166
+ transform: scale(0.96);
167
+ }
168
+ #${OVERLAY_ID} .cc-close:focus-visible {
169
+ outline: 2px solid var(--cc-accent);
170
+ outline-offset: 4px;
171
+ }
172
+ #${OVERLAY_ID} .cc-close svg {
173
+ display: block;
174
+ width: 18px;
175
+ height: 18px;
176
+ pointer-events: none;
177
+ }
178
+ #${OVERLAY_ID} iframe {
179
+ display: block;
180
+ width: 100%;
181
+ height: 100%;
182
+ border: 0;
183
+ opacity: 0;
184
+ visibility: hidden;
185
+ transition: opacity 200ms ease;
186
+ }
187
+ #${OVERLAY_ID} .cc-ready iframe {
188
+ opacity: 1;
189
+ visibility: visible;
190
+ }
191
+ #${OVERLAY_ID} .cc-spinner {
192
+ position: absolute;
193
+ inset: 0;
194
+ z-index: 1;
195
+ display: flex;
196
+ flex-direction: column;
197
+ align-items: center;
198
+ justify-content: center;
199
+ gap: 16px;
200
+ background: var(--cc-surface);
201
+ color: var(--cc-muted);
202
+ font-size: 14px;
203
+ line-height: 1.5;
204
+ transition: opacity 200ms ease;
205
+ }
206
+ #${OVERLAY_ID} .cc-spinner-ring {
207
+ width: 32px;
208
+ height: 32px;
209
+ border: 2px solid var(--cc-track);
210
+ border-top-color: var(--cc-accent);
211
+ border-radius: 50%;
212
+ animation: cc-spin 800ms linear infinite;
213
+ }
214
+ #${OVERLAY_ID} .cc-ready .cc-spinner {
215
+ opacity: 0;
216
+ pointer-events: none;
217
+ }
218
+ @keyframes cc-spin { to { transform: rotate(360deg); } }
219
+ @media (max-width: 720px) {
220
+ #${OVERLAY_ID} {
221
+ padding: 0;
222
+ background: var(--cc-surface);
223
+ -webkit-backdrop-filter: none;
224
+ backdrop-filter: none;
225
+ }
226
+ #${OVERLAY_ID} .cc-modal {
227
+ max-width: none;
228
+ height: 100vh;
229
+ height: 100dvh;
230
+ padding-top: calc(56px + env(safe-area-inset-top, 0px));
231
+ border: 0;
232
+ border-radius: 0;
233
+ box-shadow: none;
234
+ transform: none;
235
+ }
236
+ #${OVERLAY_ID} .cc-close {
237
+ top: calc(6px + env(safe-area-inset-top, 0px));
238
+ right: max(12px, env(safe-area-inset-right, 0px));
239
+ border-color: var(--cc-track);
240
+ background: transparent;
241
+ box-shadow: none;
242
+ }
243
+ }
244
+ @media (prefers-reduced-motion: reduce) {
245
+ #${OVERLAY_ID}, #${OVERLAY_ID} * {
246
+ animation: none !important;
247
+ transition: none !important;
248
+ }
249
+ #${OVERLAY_ID} .cc-modal, #${OVERLAY_ID} .cc-close:active {
250
+ transform: none;
251
+ }
252
+ }
253
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
254
+ #${OVERLAY_ID} {
255
+ background: rgba(12, 14, 18, 0.85);
256
+ -webkit-backdrop-filter: none;
257
+ backdrop-filter: none;
258
+ }
259
+ #${OVERLAY_ID} .cc-close {
260
+ background: var(--cc-surface);
261
+ -webkit-backdrop-filter: none;
262
+ backdrop-filter: none;
263
+ }
264
+ }
265
+ @media (prefers-contrast: more) {
266
+ #${OVERLAY_ID} .cc-modal, #${OVERLAY_ID} .cc-close {
267
+ border-color: var(--cc-foreground);
268
+ }
269
+ }
270
+ `;
271
+
72
272
  // src/utils.ts
73
273
  var DEFAULT_BASE_URL = "https://checkout.coincircuit.io";
74
274
  var ALLOWED_BASE_URLS = [
@@ -99,154 +299,138 @@ function extractOrigin(baseUrl) {
99
299
  return baseUrl;
100
300
  }
101
301
  }
102
- var OVERLAY_ID = "coincircuit-checkout-overlay";
103
302
  var STYLES_ID = "coincircuit-checkout-styles";
303
+ var overlayStates = /* @__PURE__ */ new WeakMap();
104
304
  function injectStyles() {
105
305
  if (document.getElementById(STYLES_ID)) return;
106
306
  const style = document.createElement("style");
107
307
  style.id = STYLES_ID;
108
- style.textContent = `
109
- #${OVERLAY_ID} {
110
- position: fixed;
111
- inset: 0;
112
- z-index: 999999;
113
- display: flex;
114
- align-items: center;
115
- justify-content: center;
116
- background: rgba(0, 0, 0, 0.6);
117
- backdrop-filter: blur(4px);
118
- opacity: 0;
119
- transition: opacity 0.2s ease;
120
- }
121
- #${OVERLAY_ID}.cc-visible {
122
- opacity: 1;
123
- }
124
- #${OVERLAY_ID} .cc-modal {
125
- position: relative;
126
- width: min(600px, 96vw);
127
- max-width: 600px;
128
- height: min(88vh, 800px);
129
- border-radius: 16px;
130
- background: #fff;
131
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
132
- opacity: 0;
133
- transform: scale(0.96);
134
- transition: opacity 0.25s ease, transform 0.25s ease;
135
- }
136
- #${OVERLAY_ID} .cc-modal.cc-ready {
137
- opacity: 1;
138
- transform: scale(1);
139
- }
140
- #${OVERLAY_ID} .cc-close {
141
- position: fixed;
142
- top: 16px;
143
- right: 16px;
144
- z-index: 1000000;
145
- width: 36px;
146
- height: 36px;
147
- border: none;
148
- border-radius: 50%;
149
- background: rgba(255, 255, 255, 0.15);
150
- backdrop-filter: blur(8px);
151
- color: #fff;
152
- font-size: 20px;
153
- line-height: 1;
154
- cursor: pointer;
155
- display: flex;
156
- align-items: center;
157
- justify-content: center;
158
- transition: background 0.15s, transform 0.15s;
159
- }
160
- #${OVERLAY_ID} .cc-close:hover {
161
- background: rgba(255, 255, 255, 0.25);
162
- transform: scale(1.1);
163
- }
164
- #${OVERLAY_ID} iframe {
165
- width: 100%;
166
- height: 100%;
167
- border: none;
168
- display: block;
169
- }
170
- #${OVERLAY_ID} .cc-spinner {
171
- position: absolute;
172
- inset: 0;
173
- display: flex;
174
- align-items: center;
175
- justify-content: center;
176
- z-index: 1;
177
- transition: opacity 0.2s;
178
- }
179
- #${OVERLAY_ID} .cc-spinner div {
180
- width: 36px;
181
- height: 36px;
182
- border: 3px solid rgba(255, 255, 255, 0.3);
183
- border-top-color: #fff;
184
- border-radius: 50%;
185
- animation: cc-spin 0.8s linear infinite;
186
- }
187
- @keyframes cc-spin { to { transform: rotate(360deg); } }
188
- @media (max-width: 720px) {
189
- #${OVERLAY_ID} .cc-modal {
190
- width: 100%;
191
- max-width: 100%;
192
- height: 100dvh;
193
- border-radius: 0;
194
- }
195
- }
196
- `;
308
+ style.textContent = OVERLAY_STYLES;
197
309
  document.head.appendChild(style);
198
310
  }
199
- function createOverlay(checkoutUrl, onCloseClick) {
311
+ function createOverlay(checkoutUrl, onCloseClick, theme = "light") {
200
312
  injectStyles();
201
313
  const overlay = document.createElement("div");
202
314
  overlay.id = OVERLAY_ID;
315
+ overlay.dataset.theme = theme;
316
+ let dismissed = false;
317
+ const dismiss = () => {
318
+ if (dismissed) return;
319
+ dismissed = true;
320
+ onCloseClick();
321
+ };
203
322
  overlay.addEventListener("click", (e) => {
204
- if (e.target === overlay) onCloseClick();
323
+ if (e.target === overlay) dismiss();
205
324
  });
325
+ const isTopmostOverlay = () => {
326
+ const overlays = document.querySelectorAll(`#${OVERLAY_ID}.cc-visible`);
327
+ return overlays[overlays.length - 1] === overlay;
328
+ };
206
329
  const onKeydown = (e) => {
207
- if (e.key === "Escape") onCloseClick();
330
+ if (!isTopmostOverlay()) return;
331
+ if (e.key === "Escape") {
332
+ e.preventDefault();
333
+ dismiss();
334
+ }
335
+ if (e.key === "Tab" && e.shiftKey && document.activeElement === closeBtn) {
336
+ e.preventDefault();
337
+ if (modal.classList.contains("cc-ready")) iframe.focus();
338
+ }
339
+ };
340
+ const onFocus = (e) => {
341
+ if (isTopmostOverlay() && !overlay.contains(e.target)) closeBtn.focus();
208
342
  };
209
343
  document.addEventListener("keydown", onKeydown);
210
- overlay.dataset.keydownAttached = "true";
211
- overlay._removeKeydown = () => document.removeEventListener("keydown", onKeydown);
344
+ document.addEventListener("focusin", onFocus);
345
+ overlayStates.set(overlay, {
346
+ removeListeners: () => {
347
+ document.removeEventListener("keydown", onKeydown);
348
+ document.removeEventListener("focusin", onFocus);
349
+ },
350
+ previousFocus: document.activeElement instanceof HTMLElement ? document.activeElement : null,
351
+ previousOverflow: document.body.style.overflow,
352
+ previousPaddingRight: document.body.style.paddingRight
353
+ });
212
354
  const modal = document.createElement("div");
213
355
  modal.className = "cc-modal";
356
+ modal.setAttribute("role", "dialog");
357
+ modal.setAttribute("aria-modal", "true");
358
+ modal.setAttribute("aria-label", "CoinCircuit checkout");
359
+ const frame = document.createElement("div");
360
+ frame.className = "cc-frame";
361
+ frame.setAttribute("aria-busy", "true");
214
362
  const spinner = document.createElement("div");
215
363
  spinner.className = "cc-spinner";
364
+ spinner.setAttribute("role", "status");
216
365
  const spinnerRing = document.createElement("div");
366
+ spinnerRing.className = "cc-spinner-ring";
367
+ spinnerRing.setAttribute("aria-hidden", "true");
368
+ const spinnerLabel = document.createElement("span");
369
+ spinnerLabel.textContent = "Loading checkout";
217
370
  spinner.appendChild(spinnerRing);
371
+ spinner.appendChild(spinnerLabel);
218
372
  const iframe = document.createElement("iframe");
219
373
  iframe.src = checkoutUrl;
220
374
  iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox");
221
375
  iframe.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
222
376
  iframe.setAttribute("title", "CoinCircuit Checkout");
223
377
  iframe.setAttribute("allow", "payment");
378
+ iframe.tabIndex = -1;
224
379
  const closeBtn = document.createElement("button");
225
380
  closeBtn.className = "cc-close";
381
+ closeBtn.type = "button";
226
382
  closeBtn.setAttribute("aria-label", "Close checkout");
227
- closeBtn.textContent = "\u2715";
228
- closeBtn.addEventListener("click", onCloseClick);
229
- modal.appendChild(iframe);
230
- overlay.appendChild(spinner);
383
+ const closeIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
384
+ closeIcon.setAttribute("viewBox", "0 0 24 24");
385
+ closeIcon.setAttribute("aria-hidden", "true");
386
+ closeIcon.setAttribute("fill", "none");
387
+ closeIcon.setAttribute("stroke", "currentColor");
388
+ closeIcon.setAttribute("stroke-width", "1.75");
389
+ closeIcon.setAttribute("stroke-linecap", "round");
390
+ const closePath = document.createElementNS("http://www.w3.org/2000/svg", "path");
391
+ closePath.setAttribute("d", "M6 6l12 12M18 6L6 18");
392
+ closeIcon.appendChild(closePath);
393
+ closeBtn.appendChild(closeIcon);
394
+ closeBtn.addEventListener("click", dismiss);
395
+ frame.appendChild(iframe);
396
+ frame.appendChild(spinner);
397
+ modal.appendChild(closeBtn);
398
+ modal.appendChild(frame);
231
399
  overlay.appendChild(modal);
232
- overlay.appendChild(closeBtn);
233
400
  return { overlay, iframe };
234
401
  }
235
402
  function showOverlay(overlay) {
236
403
  document.body.appendChild(overlay);
404
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
405
+ if (scrollbarWidth > 0) {
406
+ const padding = parseFloat(window.getComputedStyle(document.body).paddingRight) || 0;
407
+ document.body.style.paddingRight = `${padding + scrollbarWidth}px`;
408
+ }
237
409
  document.body.style.overflow = "hidden";
238
410
  overlay.offsetHeight;
239
411
  overlay.classList.add("cc-visible");
412
+ overlay.querySelector(".cc-close")?.focus({ preventScroll: true });
240
413
  }
241
414
  function removeOverlay(overlay) {
242
- overlay._removeKeydown?.();
415
+ const state = overlayStates.get(overlay);
416
+ state?.removeListeners();
243
417
  overlay.classList.remove("cc-visible");
244
- document.body.style.overflow = "";
418
+ overlay.style.pointerEvents = "none";
419
+ overlay.inert = true;
420
+ if (state) {
421
+ document.body.style.overflow = state.previousOverflow;
422
+ document.body.style.paddingRight = state.previousPaddingRight;
423
+ state.previousFocus?.focus({ preventScroll: true });
424
+ overlayStates.delete(overlay);
425
+ }
245
426
  setTimeout(() => {
246
427
  overlay.parentNode?.removeChild(overlay);
247
428
  }, 200);
248
429
  }
249
430
  function hideSpinner(overlay) {
431
+ overlay.querySelector(".cc-frame")?.setAttribute("aria-busy", "false");
432
+ const iframe = overlay.querySelector("iframe");
433
+ if (iframe) iframe.tabIndex = 0;
250
434
  const spinner = overlay.querySelector(".cc-spinner");
251
435
  if (spinner) {
252
436
  spinner.style.opacity = "0";
@@ -324,14 +508,14 @@ var CoinCircuitCheckout = class {
324
508
  const { overlay, iframe } = createOverlay(checkoutUrl, () => {
325
509
  options.onClose?.();
326
510
  this.close();
327
- });
511
+ }, options.theme);
328
512
  this.overlay = overlay;
329
513
  this.loadTimeout = setTimeout(() => {
330
514
  showErrorToast(
331
515
  "Checkout failed to load."
332
516
  );
333
517
  this.close();
334
- }, 15e3);
518
+ }, 3e4);
335
519
  iframe.addEventListener("error", () => {
336
520
  if (this.loadTimeout) clearTimeout(this.loadTimeout);
337
521
  console.error(`[CoinCircuit] iframe error after ${Math.round(performance.now() - this.openedAt)}ms`);
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CoinCircuitCheckout
3
- } from "./chunk-RJ3FMO5K.js";
3
+ } from "./chunk-FKC27TV4.js";
4
4
 
5
5
  // src/react.ts
6
6
  import { useRef, useCallback, useEffect, createElement } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coincircuit/checkout",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Embed CoinCircuit checkout in your site via iframe, with vanilla JS and React support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",