@openreceive/elements 0.1.0 → 0.2.1

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.js CHANGED
@@ -1,781 +1,30 @@
1
- import { OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS, OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES, OPENRECEIVE_CHECKOUT_DATA_SELECTORS, OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES, OPENRECEIVE_COUNTRY_STORAGE_KEY, OPENRECEIVE_CHECKOUT_ELEMENT_PARTS, OPENRECEIVE_CHECKOUT_ELEMENT_PART_SELECTORS, OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES, OPENRECEIVE_PAYMENT_WIZARD_SELECTORS, OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES, OPENRECEIVE_THEME_TOGGLE_ELEMENT_PARTS, OPENRECEIVE_THEME_TOGGLE_ELEMENT_PART_SELECTORS, OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME, copyInvoice, createCheckoutActionEvent, createCheckoutDisplayModel, createCheckoutController, createCheckoutErrorEvent, createCheckoutStatusModel, createCheckoutSnapshotFromDisplayData, createCheckoutStateFromDisplayData, createCheckoutStateEvent, createOpenReceivePaymentWizardModel, createOpenReceivePaymentWizardSelection, createCheckoutProviderCopyEvent, createOpenReceiveThemeChangeEvent, createOpenReceiveTransientFeedbackController, createOpenReceiveWizardRouteAssetDisplays, createOpenReceiveWizardRouteDisplays, createQrSvg, escapeOpenReceiveHtml, formatOpenReceiveMsats, getOpenReceiveDefaultCountryCode, getOpenReceivePaymentMethodIcon, getOpenReceiveRegionForCountry, getOpenReceiveWizardEmptyMessage, openReceiveCheckoutLabels, openReceiveCheckoutElementStyles, openWallet, openReceivePaymentMethods, openReceiveThemeToggleElementStyles, parseOpenReceiveBooleanAttribute, parseOpenReceiveOptionalInteger, parseOpenReceivePaymentMethod, parseOpenReceiveRegion, parseOpenReceiveResolvedTheme, parseOpenReceiveThemePreference, readOpenReceiveStoredCountryCode, status as deriveStatus, syncOpenReceiveStoredThemeControls, toggleOpenReceiveStoredThemeControls, updateOpenReceivePaymentWizardSelection, writeOpenReceiveStoredCountryCode } from "@openreceive/browser/internal";
2
- const DEFAULT_TAG_NAME = "openreceive-checkout";
3
- export { OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME } from "@openreceive/browser/internal";
4
- export function renderCheckoutHtml(view) {
5
- const display = createCheckoutDisplayModel(view);
6
- const checkoutState = createElementCheckoutState(view);
7
- const amountLabel = display.amountLabel === undefined
8
- ? ""
9
- : `<span part="amount">${escapeHtml(display.amountLabel)}</span>`;
10
- const fiatLabel = display.fiatLabel === undefined
11
- ? ""
12
- : `<span part="amount">${escapeHtml(display.fiatLabel)}</span>`;
13
- const statusLabel = view.status ?? (checkoutState === undefined
14
- ? deriveStatus(view)
15
- : deriveStatus(checkoutState));
16
- const stateLabel = `<span part="state" data-state="${escapeHtml(statusLabel)}">${escapeHtml(statusLabel)}</span>`;
17
- const status = checkoutState === undefined
18
- ? ""
19
- : renderElementPaymentStatusHtml(checkoutState);
20
- const statusModel = checkoutState === undefined
21
- ? undefined
22
- : createCheckoutStatusModel(checkoutState);
23
- const expired = statusModel?.phase === "expired";
24
- const wizard = expired || view.payment_wizard === false
25
- ? ""
26
- : renderOpenReceivePaymentWizardHtml(view.wizard);
27
- return `
28
- <style>${openReceiveCheckoutElementStyles}</style>
29
- <section part="root"${view.theme === undefined ? "" : ` data-theme="${escapeHtml(view.theme)}"`}>
30
- ${expired ? "" : `<div part="qr" ${OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.qr}></div>`}
31
- ${status}
32
- <div part="meta">${amountLabel}${fiatLabel}${stateLabel}</div>
33
- <div part="actions">
34
- ${expired
35
- ? `<button part="${OPENRECEIVE_CHECKOUT_ELEMENT_PARTS.startOver}" type="button">${escapeHtml(openReceiveCheckoutLabels.startOver)}</button>`
36
- : `<button part="${OPENRECEIVE_CHECKOUT_ELEMENT_PARTS.copy}" type="button">${escapeHtml(openReceiveCheckoutLabels.copyInvoice)}</button>`}
37
- </div>
38
- ${wizard}
39
- </section>
40
- `;
41
- }
42
- export function renderOpenReceiveThemeToggleHtml(label, resolvedTheme = label.toLowerCase().includes("dark") ? "dark" : "light") {
43
- return `
44
- <style>${openReceiveThemeToggleElementStyles}</style>
45
- <button
46
- aria-label="${escapeHtml(label)}"
47
- class="or-theme-toggle-${escapeHtml(resolvedTheme)}"
48
- part="${OPENRECEIVE_THEME_TOGGLE_ELEMENT_PARTS.button}"
49
- title="${escapeHtml(label)}"
50
- type="button"
51
- ${OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.themeToggle}
52
- >
53
- <span class="or-theme-toggle-track" aria-hidden="true">
54
- <span class="or-theme-toggle-icon or-theme-toggle-icon-light"></span>
55
- </span>
56
- <span class="or-theme-toggle-label">${escapeHtml(label)}</span>
57
- </button>
58
- `;
59
- }
60
- export function renderOpenReceivePaymentWizardHtml(view = {}) {
61
- const selectedCountryCode = view.selectedCountryCode ?? getOpenReceiveDefaultCountryCode();
62
- const selection = {
63
- selectedMethod: view.selectedMethod ?? null,
64
- selectedCountryCode,
65
- selectedBitcoinRoute: view.selectedBitcoinRoute ?? null,
66
- selectedCryptoRoute: view.selectedCryptoRoute ?? null,
67
- selectedRegion: view.selectedRegion ?? getOpenReceiveRegionForCountry(selectedCountryCode),
68
- countryPickerOpen: view.countryPickerOpen ?? false
69
- };
70
- const model = createOpenReceivePaymentWizardModel(selection);
71
- const { wizard } = model;
72
- const routeAssetDisplays = createOpenReceiveWizardRouteAssetDisplays(model.routeAssets, {
73
- selectedRoute: model.selectedRoute
74
- });
75
- const routeDisplays = createOpenReceiveWizardRouteDisplays(wizard.routes);
76
- const showRoutePicker = routeAssetDisplays.length > 0 &&
77
- (model.selectedRoute === null || routeDisplays.length === 0);
78
- const breadcrumbs = selection.selectedMethod === null
79
- ? ""
80
- : renderWizardBreadcrumbsHtml({
81
- method: selection.selectedMethod,
82
- selectedRoute: model.selectedRoute,
83
- routeAssets: routeAssetDisplays
84
- });
85
- const methodPicker = selection.selectedMethod === null
86
- ? `
87
- <div>
88
- <h2>${escapeHtml(openReceiveCheckoutLabels.wizardTitle)}</h2>
89
- <p>${escapeHtml(openReceiveCheckoutLabels.wizardSubtitle)}</p>
90
- </div>
91
- <div part="method-grid">
92
- ${openReceivePaymentMethods.map((method) => `
93
- <button
94
- part="method"
95
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.method}="${escapeHtml(method.id)}"
96
- type="button"
97
- >
98
- <img alt="" src="${escapeHtml(getOpenReceivePaymentMethodIcon(method.id))}">
99
- <strong>${escapeHtml(method.title)}</strong>
100
- <small>${escapeHtml(method.detail)}</small>
101
- </button>
102
- `).join("")}
103
- </div>
104
- `
105
- : "";
106
- return `
107
- <section part="wizard" ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.root}>
108
- ${methodPicker}
109
- ${breadcrumbs}
110
- ${showRoutePicker ? `
111
- <div part="route-picker">
112
- ${routeAssetDisplays.map((asset) => `
113
- <button
114
- part="route${asset.selected ? " selected" : ""}"
115
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.route}="${escapeHtml(asset.id)}"
116
- type="button"
117
- >
118
- <img alt="" src="${escapeHtml(asset.icon)}">
119
- <strong>${escapeHtml(asset.label)}</strong>
120
- <small>${escapeHtml(asset.subtitle)}</small>
121
- </button>
122
- `).join("")}
123
- </div>
124
- ` : ""}
125
- ${selection.selectedMethod === null ? "" : `
126
- <div part="wizard-results">
127
- ${routeDisplays.length === 0 ? `
128
- <p part="wizard-empty">${escapeHtml(getOpenReceiveWizardEmptyMessage(selection.selectedMethod))}</p>
129
- ` : routeDisplays.map((route) => `
130
- <section part="wizard-route">
131
- <h3>
132
- ${escapeHtml(route.title)}
133
- ${wizard.selectedRail === null ? "" : renderCountrySelectHtml({
134
- countries: model.countryDisplays,
135
- selectedCountryCode: selection.selectedCountryCode
136
- })}
137
- </h3>
138
- <div part="provider-grid">
139
- ${route.providers.map((provider) => `
140
- <article part="provider${provider.recommended ? " selected" : ""}">
141
- <div part="provider-heading">
142
- <img alt="" src="${escapeHtml(provider.icon)}">
143
- <h4>${escapeHtml(provider.name)}</h4>
144
- ${provider.recommendedLabel === null ? "" : `<span part="recommended">${escapeHtml(provider.recommendedLabel)}</span>`}
145
- </div>
146
- <p part="provider-kind">${escapeHtml(provider.kind)}</p>
147
- <div part="provider-actions">
148
- ${renderProviderOpenActionHtml(provider)}
149
- </div>
150
- </article>
151
- `).join("")}
152
- </div>
153
- </section>
154
- `).join("")}
155
- </div>
156
- `}
157
- ${renderTutorialModalHtml(routeDisplays, view.activeTutorialProviderId ?? null, view.activeTutorialIndex ?? 0, view.activeTutorialCopied ?? false)}
158
- </section>
159
- `;
160
- }
161
- function renderWizardBreadcrumbsHtml(options) {
162
- const method = openReceivePaymentMethods.find((candidate) => candidate.id === options.method);
163
- const methodLabel = method?.title ?? openReceiveCheckoutLabels.paymentMethod;
164
- const routeLabel = options.selectedRoute === null || options.routeAssets.length <= 1
165
- ? null
166
- : options.routeAssets.find((asset) => asset.id === options.selectedRoute)?.label ?? options.selectedRoute;
167
- return `
168
- <nav part="wizard-breadcrumbs" aria-label="Payment path">
169
- <button
170
- part="wizard-breadcrumb"
171
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.breadcrumb}="method"
172
- type="button"
173
- >
174
- <span>${escapeHtml(openReceiveCheckoutLabels.paymentMethod)}</span>
175
- </button>
176
- <span part="wizard-breadcrumb-separator" aria-hidden="true">/</span>
177
- ${routeLabel === null
178
- ? `<span part="wizard-breadcrumb-current">${escapeHtml(methodLabel)}</span>`
179
- : `
180
- <button
181
- part="wizard-breadcrumb"
182
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.breadcrumb}="route"
183
- type="button"
184
- >
185
- <span>${escapeHtml(methodLabel)}</span>
186
- </button>
187
- <span part="wizard-breadcrumb-separator" aria-hidden="true">/</span>
188
- <span part="wizard-breadcrumb-current">${escapeHtml(routeLabel)}</span>
189
- `}
190
- </nav>
191
- `;
192
- }
193
- function renderProviderOpenActionHtml(provider) {
194
- if (provider.tutorials.length === 0) {
195
- return `<a href="${escapeHtml(provider.url)}" rel="noreferrer" target="_blank">${escapeHtml(provider.openLabel)}</a>`;
196
- }
197
- return `
198
- <button
199
- part="provider-open"
200
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorial}="${escapeHtml(provider.id)}"
201
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorialIndex}="0"
202
- type="button"
203
- >${escapeHtml(provider.openLabel)}</button>
204
- `;
205
- }
206
- function renderTutorialModalHtml(routes, activeProviderId, activeTutorialIndex, copied) {
207
- if (activeProviderId === null)
208
- return "";
209
- const provider = routes
210
- .flatMap((route) => route.providers)
211
- .find((candidate) => candidate.id === activeProviderId);
212
- if (provider === undefined || provider.tutorials.length === 0)
213
- return "";
214
- const totalSteps = provider.tutorials.length + 1;
215
- const stepIndex = Math.max(0, Math.min(provider.tutorials.length, activeTutorialIndex));
216
- const tutorial = stepIndex === 0 ? undefined : provider.tutorials[stepIndex - 1];
217
- const previousIndex = Math.max(0, stepIndex - 1);
218
- const nextIndex = Math.min(provider.tutorials.length, stepIndex + 1);
219
- const isFinalStep = stepIndex === provider.tutorials.length;
220
- const body = stepIndex === 0
221
- ? `
222
- <div part="tutorial-intro">
223
- <img part="tutorial-provider-logo" alt="" src="${escapeHtml(provider.icon)}">
224
- <p>${escapeHtml(openReceiveCheckoutLabels.tutorialIntroPrefix)} ${escapeHtml(provider.name)}.</p>
225
- <p>${escapeHtml(openReceiveCheckoutLabels.tutorialIntroCopy)}</p>
226
- <button part="tutorial-copy" type="button">${escapeHtml(openReceiveCheckoutLabels.copyInvoice)}</button>
227
- ${copied
228
- ? `<p part="tutorial-copy-message">${escapeHtml(openReceiveCheckoutLabels.tutorialCopiedContinue)}</p>`
229
- : ""}
230
- </div>
231
- `
232
- : `
233
- <div part="tutorial-frame">
234
- <img part="tutorial-image" alt="${escapeHtml(tutorial?.caption ?? "")}" src="${escapeHtml(tutorial?.image ?? "")}">
235
- </div>
236
- <p part="tutorial-caption">${escapeHtml(tutorial?.caption ?? "")}</p>
237
- `;
238
- return `
239
- <div part="tutorial" role="dialog" aria-modal="true" aria-label="${escapeHtml(openReceiveCheckoutLabels.tutorialTitlePrefix)} ${escapeHtml(provider.name)}" tabindex="-1">
240
- <div part="tutorial-dialog">
241
- <div part="tutorial-header">
242
- <div part="tutorial-title">
243
- <img part="tutorial-header-logo" alt="" src="${escapeHtml(provider.icon)}">
244
- <h3>${escapeHtml(openReceiveCheckoutLabels.tutorialTitlePrefix)} ${escapeHtml(provider.name)}</h3>
245
- </div>
246
- <button
247
- part="tutorial-close"
248
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorial}=""
249
- type="button"
250
- aria-label="Close"
251
- >X</button>
252
- </div>
253
- ${body}
254
- <div part="tutorial-steps" aria-hidden="true">
255
- ${Array.from({ length: totalSteps }, (_, index) => `
256
- <span part="${index === stepIndex ? "tutorial-step-active" : "tutorial-step"}"></span>
257
- `).join("")}
258
- </div>
259
- <p part="tutorial-progress">Step ${stepIndex + 1} of ${totalSteps}</p>
260
- <div part="tutorial-controls">
261
- <button
262
- part="tutorial-nav"
263
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorial}="${escapeHtml(provider.id)}"
264
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorialIndex}="${previousIndex}"
265
- type="button"
266
- ${stepIndex === 0 ? "disabled" : ""}
267
- >Back</button>
268
- <button
269
- part="tutorial-nav"
270
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorial}="${isFinalStep ? "" : escapeHtml(provider.id)}"
271
- ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorialIndex}="${nextIndex}"
272
- type="button"
273
- >${escapeHtml(isFinalStep ? openReceiveCheckoutLabels.tutorialExit : "Next")}</button>
274
- </div>
275
- </div>
276
- </div>
277
- `;
278
- }
279
- function renderCountrySelectHtml(options) {
280
- return `
281
- <label part="country-select">
282
- <span>${escapeHtml(openReceiveCheckoutLabels.chooseCountry)}</span>
283
- <select ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.country}="${escapeHtml(options.selectedCountryCode)}">
284
- ${options.countries.map((country) => `
285
- <option
286
- value="${escapeHtml(country.code)}"
287
- ${country.code === options.selectedCountryCode ? "selected" : ""}
288
- >${escapeHtml(country.label)}</option>
289
- `).join("")}
290
- </select>
291
- </label>
292
- `;
293
- }
294
- function createElementCheckoutState(view) {
295
- if (view.invoice_id === undefined)
296
- return undefined;
297
- return createCheckoutStateFromDisplayData({
298
- ...view,
299
- ...(view.status === undefined
300
- ? {}
301
- : { transaction_state: transactionStateFromStatus(view.status) })
302
- });
303
- }
304
- function transactionStateFromStatus(status) {
305
- if (status === "paid")
306
- return "settled";
307
- if (status === "expired")
308
- return "expired";
309
- if (status === "failed")
310
- return "failed";
311
- return "pending";
312
- }
313
- function parseElementStatus(value) {
314
- if (value === "pending" ||
315
- value === "paid" ||
316
- value === "expired" ||
317
- value === "failed") {
318
- return value;
319
- }
320
- return undefined;
321
- }
322
- function renderElementPaymentStatusHtml(state) {
323
- const status = createCheckoutStatusModel(state);
324
- const countdown = status.countdownLabel === undefined
325
- ? ""
326
- : `<small part="countdown">${escapeHtml(status.countdownPrefix)} <strong>${escapeHtml(status.countdownLabel)}</strong></small>`;
327
- return `
328
- <div part="status">
329
- ${status.waiting ? `<span part="spinner" aria-hidden="true"></span>` : ""}
330
- <div>
331
- <strong>${escapeHtml(status.title)}</strong>
332
- <p>${escapeHtml(status.detail)}</p>
333
- ${countdown}
334
- </div>
335
- </div>
336
- `;
337
- }
338
- export function defineOpenReceiveElements(options = {}) {
339
- const registry = options.registry ?? globalThis.customElements;
340
- const HTMLElementCtor = globalThis.HTMLElement;
341
- const tagName = options.tagName ?? DEFAULT_TAG_NAME;
342
- const themeToggleTagName = options.themeToggleTagName ?? OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME;
343
- if (registry === undefined || HTMLElementCtor === undefined) {
344
- throw new Error("Custom elements are unavailable in this environment.");
345
- }
346
- class CheckoutElement extends HTMLElementCtor {
347
- selection = createOpenReceivePaymentWizardSelection({
348
- storedCountryCode: readOpenReceiveStoredCountryCode(),
349
- defaultCountryCode: getOpenReceiveDefaultCountryCode()
350
- });
351
- activeTutorialProviderId = null;
352
- activeTutorialIndex = 0;
353
- activeTutorialCopied = false;
354
- controller;
355
- announcedSettledPaymentHash;
356
- static get observedAttributes() {
357
- return [
358
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.invoiceId,
359
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.invoice,
360
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.paymentHash,
361
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.amountMsats,
362
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.fiatCurrency,
363
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.fiatValue,
364
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.status,
365
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.expiresAt,
366
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.lookupUrl,
367
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.theme,
368
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.paymentWizard
369
- ];
370
- }
371
- connectedCallback() {
372
- this.render();
373
- this.startCheckoutController();
374
- }
375
- attributeChangedCallback() {
376
- if (!this.isConnected)
377
- return;
378
- this.render();
379
- this.startCheckoutController();
380
- }
381
- disconnectedCallback() {
382
- this.stopCheckoutController();
383
- }
384
- render() {
385
- const invoice = this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.invoice);
386
- if (invoice === null || invoice === "") {
387
- this.replaceChildren();
388
- return;
389
- }
390
- const root = this.shadowRoot ?? this.attachShadow({ mode: "open" });
391
- root.innerHTML = renderCheckoutHtml({
392
- invoice_id: this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.invoiceId) ?? undefined,
393
- invoice,
394
- payment_hash: this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.paymentHash) ?? undefined,
395
- amount_msats: parseOpenReceiveOptionalInteger(this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.amountMsats), { label: "amount-msats" }),
396
- fiat_quote: readElementFiatQuote(this),
397
- status: parseElementStatus(this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.status)),
398
- expires_at: parseOpenReceiveOptionalInteger(this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.expiresAt), { label: "expires-at" }),
399
- theme: parseOpenReceiveResolvedTheme(this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.theme)),
400
- payment_wizard: parseOpenReceiveBooleanAttribute(this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.paymentWizard)),
401
- wizard: {
402
- selectedMethod: this.selection.selectedMethod,
403
- selectedCountryCode: this.selection.selectedCountryCode,
404
- selectedBitcoinRoute: this.selection.selectedBitcoinRoute,
405
- selectedCryptoRoute: this.selection.selectedCryptoRoute,
406
- selectedRegion: this.selection.selectedRegion,
407
- countryPickerOpen: this.selection.countryPickerOpen,
408
- activeTutorialProviderId: this.activeTutorialProviderId,
409
- activeTutorialIndex: this.activeTutorialIndex,
410
- activeTutorialCopied: this.activeTutorialCopied
411
- }
412
- });
413
- const copyButton = root.querySelector(OPENRECEIVE_CHECKOUT_ELEMENT_PART_SELECTORS.copy);
414
- copyButton?.addEventListener("click", () => {
415
- void (this.controller?.copyInvoice() ?? copyInvoice({ invoice, logger: options.logger }))
416
- .then(() => {
417
- showElementCopyFeedback(copyButton);
418
- this.dispatchEvent(createCheckoutActionEvent(OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.copy));
419
- })
420
- .catch((error) => this.dispatchError(error));
421
- });
422
- root.querySelector(OPENRECEIVE_CHECKOUT_ELEMENT_PART_SELECTORS.open)?.addEventListener("click", (event) => {
423
- event.preventDefault();
424
- try {
425
- this.controller?.openWallet() ?? openWallet({ invoice, logger: options.logger });
426
- this.dispatchEvent(createCheckoutActionEvent(OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.openWallet));
427
- }
428
- catch (error) {
429
- this.dispatchError(error);
430
- }
431
- });
432
- root.querySelector(OPENRECEIVE_CHECKOUT_ELEMENT_PART_SELECTORS.startOver)?.addEventListener("click", () => {
433
- this.dispatchEvent(createCheckoutActionEvent(OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.startOver));
434
- });
435
- const qrTarget = root.querySelector(OPENRECEIVE_CHECKOUT_DATA_SELECTORS.qr);
436
- if (qrTarget !== null) {
437
- void createQrSvg(invoice, {
438
- encoder: options.qrEncoder,
439
- width: 256
440
- })
441
- .then((svg) => {
442
- const qr = root.querySelector(OPENRECEIVE_CHECKOUT_DATA_SELECTORS.qr);
443
- if (qr !== null)
444
- qr.innerHTML = svg;
445
- })
446
- .catch((error) => this.dispatchError(error));
447
- }
448
- this.bindWizard(root, invoice, options.logger);
449
- }
450
- startCheckoutController() {
451
- const snapshot = this.currentCheckoutSnapshot();
452
- const lookupUrl = this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.lookupUrl);
453
- if (snapshot === undefined) {
454
- this.stopCheckoutController();
455
- return;
456
- }
457
- this.stopCheckoutController();
458
- this.controller = createCheckoutController({
459
- snapshot,
460
- ...(lookupUrl === null
461
- ? {}
462
- : { lookupUrl }),
463
- logger: options.logger,
464
- onError: (error) => this.dispatchError(error),
465
- onState: (nextState) => this.applyCheckoutState(nextState)
466
- });
467
- this.controller.start();
468
- }
469
- stopCheckoutController() {
470
- this.controller?.stop();
471
- this.controller = undefined;
472
- }
473
- currentCheckoutSnapshot() {
474
- const invoiceId = this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.invoiceId);
475
- const invoice = this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.invoice);
476
- if (invoiceId === null || invoice === null)
477
- return undefined;
478
- const amountMsats = parseOpenReceiveOptionalInteger(this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.amountMsats), { label: "amount-msats" });
479
- const expiresAt = parseOpenReceiveOptionalInteger(this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.expiresAt), { label: "expires-at" });
480
- return createCheckoutSnapshotFromDisplayData({
481
- invoice_id: invoiceId,
482
- invoice,
483
- ...(this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.paymentHash) === null
484
- ? {}
485
- : { payment_hash: this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.paymentHash) ?? undefined }),
486
- ...(amountMsats === undefined ? {} : { amount_msats: amountMsats }),
487
- ...(readElementFiatQuote(this) === undefined ? {} : { fiat_quote: readElementFiatQuote(this) }),
488
- transaction_state: transactionStateFromStatus(parseElementStatus(this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.status)) ?? "pending"),
489
- ...(expiresAt === undefined ? {} : { expires_at: expiresAt }),
490
- });
491
- }
492
- applyCheckoutState(state) {
493
- this.setAttributeIfChanged(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.status, deriveStatus(state));
494
- if (state.expires_at !== undefined) {
495
- this.setAttributeIfChanged(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.expiresAt, String(state.expires_at));
496
- }
497
- this.render();
498
- this.dispatchEvent(createCheckoutStateEvent(OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.state, state));
499
- if (state.settled && state.payment_hash !== this.announcedSettledPaymentHash) {
500
- this.announcedSettledPaymentHash = state.payment_hash;
501
- this.dispatchEvent(createCheckoutStateEvent(OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.settled, state));
502
- }
503
- }
504
- setAttributeIfChanged(name, value) {
505
- if (this.getAttribute(name) !== value)
506
- this.setAttribute(name, value);
507
- }
508
- bindWizard(root, invoice, logger) {
509
- root.querySelectorAll(OPENRECEIVE_PAYMENT_WIZARD_SELECTORS.method).forEach((button) => {
510
- button.addEventListener("click", () => {
511
- const method = parseOpenReceivePaymentMethod(button.getAttribute(OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.method));
512
- if (method === null)
513
- return;
514
- this.selection = updateOpenReceivePaymentWizardSelection(this.selection, {
515
- type: "select_method",
516
- method,
517
- storedCountryCode: method === "card" || method === "bank"
518
- ? readOpenReceiveStoredCountryCode()
519
- : undefined
520
- });
521
- this.render();
522
- });
523
- });
524
- root.querySelectorAll(OPENRECEIVE_PAYMENT_WIZARD_SELECTORS.breadcrumb).forEach((button) => {
525
- button.addEventListener("click", () => {
526
- const target = button.getAttribute(OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.breadcrumb);
527
- if (target === "method") {
528
- this.selection = updateOpenReceivePaymentWizardSelection(this.selection, {
529
- type: "change_method"
530
- });
531
- this.render();
532
- return;
533
- }
534
- if (target === "route") {
535
- this.selection = updateOpenReceivePaymentWizardSelection(this.selection, {
536
- type: "change_route"
537
- });
538
- this.render();
539
- }
540
- });
541
- });
542
- root.querySelectorAll(OPENRECEIVE_PAYMENT_WIZARD_SELECTORS.region).forEach((button) => {
543
- button.addEventListener("click", () => {
544
- const region = parseOpenReceiveRegion(button.getAttribute(OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.region));
545
- if (region === null)
546
- return;
547
- this.selection = updateOpenReceivePaymentWizardSelection(this.selection, {
548
- type: "select_region",
549
- region
550
- });
551
- this.render();
552
- });
553
- });
554
- root.querySelectorAll(OPENRECEIVE_PAYMENT_WIZARD_SELECTORS.country).forEach((button) => {
555
- const selectCountry = () => {
556
- const countryCode = button instanceof HTMLSelectElement
557
- ? button.value
558
- : button.getAttribute(OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.country);
559
- if (countryCode === null)
560
- return;
561
- this.selection = updateOpenReceivePaymentWizardSelection(this.selection, {
562
- type: "select_country",
563
- countryCode
564
- });
565
- writeOpenReceiveStoredCountryCode(countryCode, {
566
- storageKey: OPENRECEIVE_COUNTRY_STORAGE_KEY
567
- });
568
- this.render();
569
- };
570
- if (button instanceof HTMLSelectElement) {
571
- button.addEventListener("change", selectCountry);
572
- return;
573
- }
574
- button.addEventListener("click", selectCountry);
575
- button.addEventListener("keydown", (event) => {
576
- if (!(event instanceof KeyboardEvent))
577
- return;
578
- if (event.key !== "Enter" && event.key !== " ")
579
- return;
580
- event.preventDefault();
581
- selectCountry();
582
- });
583
- });
584
- root.querySelector(OPENRECEIVE_PAYMENT_WIZARD_SELECTORS.switchCountry)?.addEventListener("click", () => {
585
- this.selection = updateOpenReceivePaymentWizardSelection(this.selection, {
586
- type: "open_country_picker"
587
- });
588
- this.render();
589
- });
590
- root.querySelectorAll(OPENRECEIVE_PAYMENT_WIZARD_SELECTORS.route).forEach((button) => {
591
- button.addEventListener("click", () => {
592
- const route = button.getAttribute(OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.route);
593
- if (route === null)
594
- return;
595
- this.selection = updateOpenReceivePaymentWizardSelection(this.selection, {
596
- type: "select_route",
597
- route
598
- });
599
- this.render();
600
- });
601
- });
602
- root.querySelectorAll(OPENRECEIVE_PAYMENT_WIZARD_SELECTORS.providerTutorial).forEach((button) => {
603
- button.addEventListener("click", () => {
604
- const providerId = button.getAttribute(OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorial);
605
- if (providerId === null)
606
- return;
607
- if (providerId === "") {
608
- this.activeTutorialProviderId = null;
609
- this.activeTutorialIndex = 0;
610
- this.activeTutorialCopied = false;
611
- this.render();
612
- return;
613
- }
614
- const index = Number(button.getAttribute(OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorialIndex) ?? "0");
615
- if (this.activeTutorialProviderId !== providerId) {
616
- this.activeTutorialCopied = false;
617
- }
618
- this.activeTutorialProviderId = providerId;
619
- this.activeTutorialIndex = Number.isSafeInteger(index) && index >= 0 ? index : 0;
620
- this.render();
621
- });
622
- });
623
- root.querySelector('[part="tutorial-copy"]')?.addEventListener("click", () => {
624
- void copyInvoice({ invoice, logger })
625
- .then(() => {
626
- this.activeTutorialCopied = true;
627
- if (this.activeTutorialProviderId !== null) {
628
- this.dispatchEvent(createCheckoutProviderCopyEvent(this.activeTutorialProviderId));
629
- }
630
- this.dispatchEvent(createCheckoutActionEvent(OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.copy));
631
- this.render();
632
- })
633
- .catch((error) => this.dispatchError(error));
634
- });
635
- const tutorial = root.querySelector('[part="tutorial"]');
636
- tutorial?.addEventListener("click", (event) => {
637
- if (event.target !== event.currentTarget)
638
- return;
639
- this.activeTutorialProviderId = null;
640
- this.activeTutorialIndex = 0;
641
- this.activeTutorialCopied = false;
642
- this.render();
643
- });
644
- tutorial?.addEventListener("keydown", (event) => {
645
- if (!(event instanceof KeyboardEvent))
646
- return;
647
- if (event.key !== "Escape" || this.activeTutorialProviderId === null)
648
- return;
649
- this.activeTutorialProviderId = null;
650
- this.activeTutorialIndex = 0;
651
- this.activeTutorialCopied = false;
652
- this.render();
653
- });
654
- if (tutorial instanceof HTMLElement)
655
- tutorial.focus();
656
- }
657
- dispatchError(error) {
658
- this.dispatchEvent(createCheckoutErrorEvent(error));
659
- }
660
- }
661
- class OpenReceiveThemeToggleElement extends HTMLElementCtor {
662
- observer;
663
- static get observedAttributes() {
664
- return [
665
- OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.rootSelector,
666
- OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.checkoutSelector,
667
- OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.defaultTheme,
668
- OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.storageKey
669
- ];
670
- }
671
- connectedCallback() {
672
- this.render();
673
- this.startObserver();
674
- this.syncTheme();
675
- }
676
- attributeChangedCallback() {
677
- if (!this.isConnected)
678
- return;
679
- this.render();
680
- this.syncTheme();
681
- }
682
- disconnectedCallback() {
683
- this.observer?.disconnect();
684
- this.observer = undefined;
685
- }
686
- render() {
687
- const root = this.shadowRoot ?? this.attachShadow({ mode: "open" });
688
- const theme = this.syncTheme();
689
- root.innerHTML = renderOpenReceiveThemeToggleHtml(theme.toggleLabel, theme.resolvedTheme);
690
- root.querySelector(OPENRECEIVE_THEME_TOGGLE_ELEMENT_PART_SELECTORS.button)?.addEventListener("click", () => {
691
- const nextTheme = toggleOpenReceiveStoredThemeControls(this.themeTargets(), this.themeOptions());
692
- this.setAttributeIfChanged(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.theme, nextTheme.resolvedTheme);
693
- this.dispatchEvent(createOpenReceiveThemeChangeEvent(nextTheme));
694
- this.render();
695
- });
696
- }
697
- syncTheme() {
698
- const theme = syncOpenReceiveStoredThemeControls(this.themeTargets(), this.themeOptions());
699
- this.setAttributeIfChanged(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.theme, theme.resolvedTheme);
700
- return theme;
701
- }
702
- startObserver() {
703
- if (this.observer !== undefined)
704
- return;
705
- const MutationObserverCtor = globalThis.MutationObserver;
706
- if (MutationObserverCtor === undefined || this.ownerDocument.body === null)
707
- return;
708
- this.observer = new MutationObserverCtor(() => {
709
- this.syncTheme();
710
- });
711
- this.observer.observe(this.ownerDocument.body, {
712
- childList: true,
713
- subtree: true
714
- });
715
- }
716
- themeTargets() {
717
- const rootSelector = this.getAttribute(OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.rootSelector);
718
- const checkoutSelector = this.getAttribute(OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.checkoutSelector);
719
- const button = this.shadowRoot?.querySelector(OPENRECEIVE_THEME_TOGGLE_ELEMENT_PART_SELECTORS.button) ??
720
- null;
721
- return {
722
- root: rootSelector === null ? undefined : this.ownerDocument.querySelector(rootSelector),
723
- checkout: checkoutSelector === null ? undefined : this.ownerDocument.querySelector(checkoutSelector),
724
- toggle: button
725
- };
726
- }
727
- themeOptions() {
728
- const defaultTheme = parseOpenReceiveThemePreference(this.getAttribute(OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.defaultTheme));
729
- return {
730
- ...(defaultTheme === undefined ? {} : { defaultTheme }),
731
- ...(this.getAttribute(OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.storageKey) === null
732
- ? {}
733
- : {
734
- storageKey: this.getAttribute(OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.storageKey) ??
735
- undefined
736
- })
737
- };
738
- }
739
- setAttributeIfChanged(name, value) {
740
- if (this.getAttribute(name) !== value)
741
- this.setAttribute(name, value);
742
- }
743
- }
744
- if (registry.get(tagName) === undefined) {
745
- registry.define(tagName, CheckoutElement);
746
- }
747
- if (registry.get(themeToggleTagName) === undefined) {
748
- registry.define(themeToggleTagName, OpenReceiveThemeToggleElement);
749
- }
750
- }
751
- export const formatMsats = formatOpenReceiveMsats;
752
- const elementCopyFeedbackControllers = new WeakMap();
753
- const escapeHtml = escapeOpenReceiveHtml;
754
- function readElementFiatQuote(element) {
755
- const currency = element.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.fiatCurrency);
756
- const value = element.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.fiatValue);
757
- if (currency === null || value === null)
758
- return undefined;
759
- return {
760
- fiat: {
761
- currency,
762
- value
763
- }
764
- };
765
- }
766
- function showElementCopyFeedback(button) {
767
- if (button === null)
768
- return;
769
- let controller = elementCopyFeedbackControllers.get(button);
770
- if (controller === undefined) {
771
- controller = createOpenReceiveTransientFeedbackController({
772
- resetValue: openReceiveCheckoutLabels.copyInvoice,
773
- onValue: (label) => {
774
- if (button.isConnected)
775
- button.textContent = label;
776
- }
777
- });
778
- elementCopyFeedbackControllers.set(button, controller);
779
- }
780
- controller.show(openReceiveCheckoutLabels.copied);
781
- }
1
+ import {
2
+ OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES,
3
+ OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS,
4
+ OPENRECEIVE_CHECKOUT_ELEMENT_TAG_NAME,
5
+ OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES,
6
+ OPENRECEIVE_THEME_TOGGLE_ELEMENT_EVENTS,
7
+ OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME,
8
+ createThemeToggleElement,
9
+ createTransactionDetailsElement,
10
+ defineElements,
11
+ renderCheckoutCreateErrorHtml,
12
+ renderCheckoutHtml,
13
+ renderThemeToggleHtml,
14
+ renderTransactionDetailsHtml
15
+ } from "./chunk-46RMHGRA.js";
16
+ export {
17
+ OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES,
18
+ OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS,
19
+ OPENRECEIVE_CHECKOUT_ELEMENT_TAG_NAME,
20
+ OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES,
21
+ OPENRECEIVE_THEME_TOGGLE_ELEMENT_EVENTS,
22
+ OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME,
23
+ createThemeToggleElement,
24
+ createTransactionDetailsElement,
25
+ defineElements,
26
+ renderCheckoutCreateErrorHtml,
27
+ renderCheckoutHtml,
28
+ renderThemeToggleHtml,
29
+ renderTransactionDetailsHtml
30
+ };