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