@openreceive/elements 0.2.4 → 0.3.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.
@@ -13,6 +13,7 @@ import {
13
13
  import {
14
14
  buildMethodGridEntries as buildMethodGridEntries2,
15
15
  copyInvoice,
16
+ createAssetBaseUrlResolver,
16
17
  createCheckoutActionEvent,
17
18
  createCheckoutController,
18
19
  createCheckoutErrorEvent,
@@ -27,7 +28,6 @@ import {
27
28
  createQrSvg,
28
29
  deriveStatus as deriveStatus2,
29
30
  enterCheckoutResumePath,
30
- findSwapGridGroup as findSwapGridGroup2,
31
31
  getSwapRefundFormError,
32
32
  OPENRECEIVE_CHECKOUT_DATA_SELECTORS,
33
33
  OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES as OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3,
@@ -42,18 +42,16 @@ import {
42
42
  OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME,
43
43
  paymentMethods as paymentMethods2,
44
44
  orClasses as orClasses7,
45
- overlaySwapRefundStaging,
46
45
  parseBooleanAttribute,
47
- parseMethodPickerKey,
48
46
  parseOptionalInteger,
49
47
  parsePaymentMethod,
50
48
  parseResolvedTheme,
51
49
  parseThemePreference,
52
- requestSwapRefund,
50
+ resolveWizardSelection,
51
+ selectCurrentSwapInvoice,
53
52
  syncStoredThemeControls,
54
53
  toggleStoredThemeControls,
55
- updatePaymentWizardSelection,
56
- updateSelectedSwapNetworks
54
+ updatePaymentWizardSelection
57
55
  } from "@openreceive/browser/headless";
58
56
 
59
57
  // src/element-checkout-session.ts
@@ -121,18 +119,20 @@ function createElementCheckoutSession(host) {
121
119
  });
122
120
  },
123
121
  onSnapshot: publishSnapshot,
124
- swapPrefix: () => host.resolvePollPrefix(currentReference()),
125
- fetch: () => globalThis.fetch,
126
- swapSelection: host.swapSelection,
127
- // A swap attempt is NOT written back as attributes: a bolt11 attribute
128
- // would take the element out of create mode. It re-keys the poll
129
- // controller onto the merged snapshot and nothing else.
130
- onSwapStarted: (invoice) => {
131
- const reference = currentReference();
132
- if (reference === void 0) return;
133
- const previous = host.latestCheckoutSnapshot() ?? host.currentCheckoutSnapshot();
134
- host.handleControllerSnapshot(mergeAttemptIntoCheckout(invoice, previous, reference));
135
- host.startCheckoutController();
122
+ swap: {
123
+ selection: host.swapSelection,
124
+ prefix: () => host.resolvePollPrefix(currentReference()),
125
+ fetch: () => globalThis.fetch,
126
+ // A swap attempt is NOT written back as attributes: a bolt11 attribute
127
+ // would take the element out of create mode. It re-keys the poll
128
+ // controller onto the merged snapshot and nothing else.
129
+ onStarted: (invoice) => {
130
+ const reference = currentReference();
131
+ if (reference === void 0) return;
132
+ const previous = host.latestCheckoutSnapshot() ?? host.currentCheckoutSnapshot();
133
+ host.handleControllerSnapshot(mergeAttemptIntoCheckout(invoice, previous, reference));
134
+ host.startCheckoutController();
135
+ }
136
136
  },
137
137
  logger: host.logger,
138
138
  onError: (error) => host.dispatchError(error),
@@ -393,6 +393,7 @@ import {
393
393
  escapeHtml as escapeHtml6,
394
394
  formatAmountCaption,
395
395
  OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES,
396
+ OPENRECEIVE_CHECKOUT_ELEMENT_SLOTS,
396
397
  OPENRECEIVE_CHECKOUT_ELEMENT_PARTS,
397
398
  OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES as OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES6,
398
399
  OPENRECEIVE_THEME_TOGGLE_ELEMENT_PARTS,
@@ -405,13 +406,12 @@ import {
405
406
  assetButtonClasses,
406
407
  buildMethodGridEntries,
407
408
  checkoutLabels as checkoutLabels5,
409
+ createMethodGridDisplay,
408
410
  createPaymentWizardModel,
409
411
  createSwapUnavailableModel,
410
412
  createWizardRouteAssetDisplays,
411
413
  createWizardRouteDisplays,
412
414
  escapeHtml as escapeHtml5,
413
- findSwapGridGroup,
414
- formatChooseNetworkHeading,
415
415
  formatNetworkSummary,
416
416
  getNetworkIcon,
417
417
  getPaymentMethodIcon,
@@ -423,12 +423,8 @@ import {
423
423
  networkSummaryIconClasses,
424
424
  OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES as OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES5,
425
425
  orClasses as orClasses5,
426
- paymentAccentId,
427
426
  paymentMethods,
428
- swapAssetMatchesRoute as swapAssetMatchesRoute2,
429
- swapGroupLimitOption,
430
- swapPickerKey,
431
- wizardNetworkGroupIds
427
+ swapAssetMatchesRoute as swapAssetMatchesRoute2
432
428
  } from "@openreceive/browser/headless";
433
429
 
434
430
  // src/render-provider-tutorial.ts
@@ -621,15 +617,20 @@ function renderElementSwapActionsHtml(routeKey, options, view) {
621
617
  </div>
622
618
  `;
623
619
  }
624
- function elementsSwapLimitMessage(option, view) {
625
- const checkout = view.amountMsats === void 0 ? void 0 : {
620
+ function elementsSwapLimitContext(view) {
621
+ if (view.amountMsats === void 0) return void 0;
622
+ return {
626
623
  amount_msats: view.amountMsats,
627
624
  ...view.fiat?.currency === void 0 || view.fiat.value === void 0 ? {} : { fiat: { currency: view.fiat.currency, value: view.fiat.value } }
628
625
  };
629
- return swapOptionLimitMessage(option, checkout);
626
+ }
627
+ function elementsSwapLimitMessage(option, view) {
628
+ return swapOptionLimitMessage(option, elementsSwapLimitContext(view));
630
629
  }
631
630
  function renderElementSwapPanelHtml(invoice, view = {}) {
632
- const display = createSwapDisplayModel(invoice);
631
+ const display = createSwapDisplayModel(invoice, {
632
+ ...view.resumable === void 0 ? {} : { resumable: view.resumable }
633
+ });
633
634
  if (display === void 0) return "";
634
635
  const backButton = `
635
636
  <button part="swap-back" class="${orClasses4.swapBack}" ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES4.swapBack} type="button">${escapeHtml4(checkoutLabels4.payWithLightningInstead)}</button>
@@ -677,11 +678,12 @@ function renderElementSwapPanelHtml(invoice, view = {}) {
677
678
  <div part="swap-qr" class="${orClasses4.swapQr}" ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES4.swapQr}="${escapeHtml4(display.qrPayload)}"></div>
678
679
  <div part="swap-deposit-side" class="${orClasses4.swapDepositSide}">
679
680
  <dl part="swap-details" class="${orClasses4.swapDetails}">
680
- ${renderElementSwapCopyDetailHtml("Address", display.depositAddress, {
681
- selectable: true
682
- })}
683
- ${display.depositMemo === void 0 ? "" : renderElementSwapCopyDetailHtml("Memo", display.depositMemo)}
684
- ${renderElementSwapCopyDetailHtml("Amount", display.depositAmount, { selectable: true })}
681
+ ${display.copyRows.map(
682
+ (row) => renderElementSwapCopyDetailHtml(row.label, row.copyValue ?? row.value, {
683
+ selectable: row.selectable,
684
+ displayValue: row.value
685
+ })
686
+ ).join("")}
685
687
  </dl>
686
688
  ${waitingStatus}
687
689
  ${feeBreakdown}
@@ -786,7 +788,7 @@ function renderElementSwapPanelHtml(invoice, view = {}) {
786
788
  </form>
787
789
  `}
788
790
  ${supportDetails}
789
- ${renderElementSwapRefundReturnWarningHtml()}
791
+ ${renderElementSwapRefundReturnWarningHtml(display)}
790
792
  </section>
791
793
  `;
792
794
  }
@@ -801,7 +803,7 @@ function renderElementSwapPanelHtml(invoice, view = {}) {
801
803
  ${display.refundTxId === void 0 ? "" : renderElementSwapCopyDetailHtml("Refund transaction", display.refundTxId, { kind: "tx", payInAsset: display.payInAsset })}
802
804
  </dl>
803
805
  ${supportDetails}
804
- ${renderElementSwapRefundReturnWarningHtml()}
806
+ ${renderElementSwapRefundReturnWarningHtml(display)}
805
807
  </section>
806
808
  `;
807
809
  }
@@ -814,9 +816,9 @@ function renderElementSwapPanelHtml(invoice, view = {}) {
814
816
  </section>
815
817
  `;
816
818
  }
817
- function renderElementSwapRefundReturnWarningHtml() {
819
+ function renderElementSwapRefundReturnWarningHtml(display) {
818
820
  return `
819
- <p part="swap-refund-return" class="${orClasses4.swapWarning}">${escapeHtml4(checkoutLabels4.refundReturnWarning)}</p>
821
+ <p part="swap-refund-return" class="${orClasses4.swapWarning}">${escapeHtml4(display.refundReturnLabel)}</p>
820
822
  `;
821
823
  }
822
824
  function renderElementSwapRefundFactsHtml(display) {
@@ -917,9 +919,6 @@ function wizardStartingAsset(view) {
917
919
  const asset = view.startingSwapAsset;
918
920
  return asset !== void 0 && asset !== null && asset.length > 0 ? asset : void 0;
919
921
  }
920
- function swapGroupIsStarting(group, startingAsset) {
921
- return startingAsset !== void 0 && group.options.some((option) => option.pay_in_asset === startingAsset);
922
- }
923
922
  function renderElementSwapUnavailableHtml(quote, checkout) {
924
923
  const model = createSwapUnavailableModel(quote, checkout);
925
924
  return `<section part="swap-panel" class="${orClasses5.swapPanel}">
@@ -1081,49 +1080,33 @@ function renderPaymentWizardHtml(view = {}) {
1081
1080
  `;
1082
1081
  }
1083
1082
  function renderElementCompactPaymentSelectorHtml(swapAssetOptions, view) {
1084
- const entries = buildMethodGridEntries(paymentMethods, swapAssetOptions);
1083
+ const limitContext = elementsSwapLimitContext(view);
1084
+ const display = createMethodGridDisplay({
1085
+ entries: buildMethodGridEntries(paymentMethods, swapAssetOptions),
1086
+ selectedPickerKey: view.selectedPickerKey ?? null,
1087
+ selectedAssetByGroup: view.selectedSwapAssetByGroup ?? {},
1088
+ startingAsset: wizardStartingAsset(view) ?? null,
1089
+ ...limitContext === void 0 ? {} : { checkout: limitContext }
1090
+ });
1085
1091
  const currenciesLoading = view.currenciesLoading === true && swapAssetOptions.length === 0;
1086
- const selectedKey = view.selectedPickerKey ?? null;
1087
- const selectedGroup = findSwapGridGroup(entries, selectedKey);
1088
- const networkRequired = selectedGroup !== void 0 && selectedGroup.options.length > 1;
1089
- const selectedNetworks = view.selectedSwapNetworks ?? {};
1090
- const selectedGroupKey = selectedGroup?.label.trim().toUpperCase();
1091
- const selectedNetworkAsset = selectedGroupKey === void 0 ? void 0 : selectedNetworks[selectedGroupKey];
1092
- const selectedNetworkOption = selectedGroup === void 0 || selectedNetworkAsset === void 0 ? void 0 : selectedGroup.options.find((option) => option.pay_in_asset === selectedNetworkAsset);
1093
- const startingAsset = wizardStartingAsset(view);
1094
- const gridBusy = startingAsset !== void 0;
1095
- let continueDisabled = selectedNetworkOption === void 0 || gridBusy;
1096
- let continueAttr = "";
1097
- let continueLabel = escapeHtml5(checkoutLabels5.continue);
1098
- const continueStarting = selectedNetworkOption !== void 0 && selectedNetworkOption.pay_in_asset === startingAsset;
1099
- if (selectedNetworkOption !== void 0) {
1100
- const disabled = selectedNetworkOption.available === false;
1101
- const limitMessage = elementsSwapLimitMessage(selectedNetworkOption, view);
1102
- continueDisabled = disabled || gridBusy;
1103
- continueAttr = disabled || gridBusy ? "" : `${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES5.swapStart}="${escapeHtml5(selectedNetworkOption.pay_in_asset)}"`;
1104
- if (disabled && limitMessage !== void 0) continueLabel = escapeHtml5(limitMessage);
1105
- else if (continueStarting) continueLabel = escapeHtml5(checkoutLabels5.preparingPayment);
1106
- } else if (networkRequired) {
1107
- continueDisabled = true;
1108
- }
1092
+ const { gridBusy, networkRequired, selectedGroup, continueTarget } = display;
1109
1093
  const continueButton = (className) => renderElementMethodConfirmHtml({
1110
1094
  className,
1111
- disabled: continueDisabled,
1112
- starting: continueStarting,
1113
- swapStartAsset: continueAttr.length === 0 || selectedNetworkOption === void 0 ? void 0 : selectedNetworkOption.pay_in_asset,
1114
- label: continueLabel
1095
+ disabled: continueTarget === void 0 || continueTarget.disabled,
1096
+ starting: continueTarget?.starting === true,
1097
+ swapStartAsset: continueTarget === void 0 || continueTarget.disabled ? void 0 : continueTarget.payInAsset,
1098
+ label: escapeHtml5(continueTarget?.label ?? checkoutLabels5.continue)
1115
1099
  });
1116
- const tiles = entries.map((entry) => {
1100
+ const tiles = display.entries.map((entry) => {
1117
1101
  if (entry.kind === "method") {
1118
1102
  const method = entry.method;
1119
- const accent = paymentAccentId(method.id);
1120
1103
  return `
1121
1104
  <button
1122
1105
  part="method"
1123
1106
  type="button"
1124
- class="${assetButtonClasses({ accent, selected: false, disabled: gridBusy })}"
1107
+ class="${assetButtonClasses({ accent: entry.accent, selected: false, disabled: entry.disabled })}"
1125
1108
  ${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES5.method}="${escapeHtml5(method.id)}"
1126
- ${gridBusy ? 'disabled aria-disabled="true"' : ""}
1109
+ ${entry.disabled ? 'disabled aria-disabled="true"' : ""}
1127
1110
  >
1128
1111
  <span aria-hidden="true" class="${orClasses5.methodIconWrap}">
1129
1112
  <img class="${orClasses5.methodIcon}" alt="" src="${escapeHtml5(getPaymentMethodIcon(method.id, view.resolveAssetUrl))}">
@@ -1136,8 +1119,8 @@ function renderElementCompactPaymentSelectorHtml(swapAssetOptions, view) {
1136
1119
  return renderElementSwapMethodGroupHtml(
1137
1120
  entry.group,
1138
1121
  view,
1139
- selectedKey,
1140
- selectedKey === swapPickerKey(entry.group.label) ? continueButton(orClasses5.methodConfirmDesktop) : void 0
1122
+ gridBusy,
1123
+ entry.group.selected ? continueButton(orClasses5.methodConfirmDesktop) : void 0
1141
1124
  );
1142
1125
  }).join("");
1143
1126
  const loadingTile = currenciesLoading ? `<div part="currencies-loading" role="status" aria-live="polite" class="${orClasses5.methodCurrenciesLoading}">
@@ -1164,12 +1147,7 @@ function renderElementCompactPaymentSelectorHtml(swapAssetOptions, view) {
1164
1147
  `;
1165
1148
  }
1166
1149
  function renderElementNetworkSelectorHtml(group, view, continueButtonHtml, mobile) {
1167
- const accent = paymentAccentId(group.label);
1168
- const groupKey = group.label.trim().toUpperCase();
1169
- const selectedNetworks = view.selectedSwapNetworks ?? {};
1170
- const selectedAsset = selectedNetworks[groupKey];
1171
- const selectedOption = selectedAsset === void 0 ? void 0 : group.options.find((option) => option.pay_in_asset === selectedAsset);
1172
- const { panelId, headingId } = wizardNetworkGroupIds(groupKey);
1150
+ const { accent, groupKey, panelId, headingId, selectedOption } = group;
1173
1151
  const networkButtons = group.options.map((option) => {
1174
1152
  const optionDisabled = option.available === false;
1175
1153
  const optionSelected = option.pay_in_asset === selectedOption?.pay_in_asset;
@@ -1211,7 +1189,7 @@ function renderElementNetworkSelectorHtml(group, view, continueButtonHtml, mobil
1211
1189
  <div class="${orClasses5.methodNetworkLayout}">
1212
1190
  <div>
1213
1191
  <h3 id="${headingId}" class="${orClasses5.methodNetworkHeading}">${escapeHtml5(
1214
- formatChooseNetworkHeading(group.label)
1192
+ group.heading
1215
1193
  )}</h3>
1216
1194
  <p class="${orClasses5.methodNetworkHint}">${escapeHtml5(
1217
1195
  checkoutLabels5.selectNetworkToContinue
@@ -1238,25 +1216,19 @@ function renderElementMethodConfirmHtml(options) {
1238
1216
  ${options.starting ? 'aria-busy="true"' : ""}
1239
1217
  >${options.starting ? `<span part="spinner" class="${orClasses5.continueSpinner}" aria-hidden="true"></span>` : ""}${options.label}</button>`;
1240
1218
  }
1241
- function renderElementSwapMethodGroupHtml(group, view, selectedKey, continueButtonHtml) {
1242
- const selectedNetworks = view.selectedSwapNetworks ?? {};
1243
- const groupKey = group.label.trim().toUpperCase();
1244
- const pickerKey = swapPickerKey(group.label);
1245
- const selected = selectedKey === pickerKey;
1246
- const displayOption = group.options.find((option) => option.available !== false) ?? group.options[0];
1247
- if (displayOption === void 0) return "";
1248
- const multiNetwork = group.options.length > 1;
1249
- const selectedAsset = selectedNetworks[groupKey];
1250
- const selectedOption = selectedAsset === void 0 ? void 0 : group.options.find((option) => option.pay_in_asset === selectedAsset);
1251
- const activeOption = selectedOption ?? displayOption;
1252
- const startingAsset = wizardStartingAsset(view);
1253
- const starting = swapGroupIsStarting(group, startingAsset);
1254
- const gridBusy = startingAsset !== void 0;
1255
- const disabled = group.options.every((option) => option.available === false);
1256
- const accent = paymentAccentId(group.label);
1257
- const limitOption = disabled ? swapGroupLimitOption(group.options) ?? activeOption : activeOption;
1258
- const limitMessage = elementsSwapLimitMessage(limitOption, view);
1259
- const { panelId } = wizardNetworkGroupIds(groupKey);
1219
+ function renderElementSwapMethodGroupHtml(group, view, gridBusy, continueButtonHtml) {
1220
+ const {
1221
+ pickerKey,
1222
+ selected,
1223
+ displayOption,
1224
+ selectedOption,
1225
+ multiNetwork,
1226
+ starting,
1227
+ disabled,
1228
+ accent,
1229
+ limitMessage,
1230
+ panelId
1231
+ } = group;
1260
1232
  const networkDetail = !disabled && multiNetwork ? selected && selectedOption !== void 0 ? `${escapeHtml5(selectedOption.network_label)} network` : escapeHtml5(checkoutLabels5.selectNetwork) : void 0;
1261
1233
  const mobileReveal = multiNetwork ? `
1262
1234
  <div class="${orClasses5.methodNetworkRevealAnim} ${selected ? orClasses5.methodNetworkRevealAnimOpen : orClasses5.methodNetworkRevealAnimClosed}">
@@ -1408,9 +1380,12 @@ function renderCheckoutHtml(view) {
1408
1380
  renderTransactionDetailsHtml(checkoutState ?? { ...view, rail: view.rail ?? "lightning" })
1409
1381
  ) : expired ? `<div part="actions" class="${orClasses6.actions}">${startOverButton}</div>` : `<div part="actions" class="${orClasses6.actions}">${copyButton}${decodeButton}</div>`;
1410
1382
  const resolvedTheme = view.theme ?? "light";
1383
+ const orderDescription = view.description === void 0 || view.description === "" ? "" : `<p part="order-description" ${OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.orderDescription} class="${orClasses6.orderDescription}">${escapeHtml6(view.description)}</p>`;
1411
1384
  return `
1412
1385
  ${styleTag(view.inlineStyles)}
1413
1386
  <section part="root" data-theme="${escapeHtml6(resolvedTheme)}" class="${orClasses6.root}">
1387
+ <slot name="${OPENRECEIVE_CHECKOUT_ELEMENT_SLOTS.order}"></slot>
1388
+ ${orderDescription}
1414
1389
  ${hideLightning ? "" : `<div part="payment-layout" class="${paymentLayoutClass}">
1415
1390
  ${lightningPane}
1416
1391
  <div part="payment-info" class="${orClasses6.paymentInfo}">
@@ -1505,6 +1480,7 @@ function checkoutSnapshotDisplayKey(snapshot) {
1505
1480
  )
1506
1481
  });
1507
1482
  }
1483
+ var warnedAssetSeamConflict = false;
1508
1484
  function defineElements(options = {}) {
1509
1485
  const registry = options.registry ?? globalThis.customElements;
1510
1486
  const HTMLElementCtor = globalThis.HTMLElement;
@@ -1520,7 +1496,7 @@ function defineElements(options = {}) {
1520
1496
  activeTutorialCopied = false;
1521
1497
  swapOptions = [];
1522
1498
  swapOptionsLoaded = false;
1523
- selectedSwapNetworks = {};
1499
+ selectedSwapAssetByGroup = {};
1524
1500
  selectedPickerKey = null;
1525
1501
  selectedSwapAsset = null;
1526
1502
  startedSwapInvoice;
@@ -1604,9 +1580,11 @@ function defineElements(options = {}) {
1604
1580
  OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.syncUrl,
1605
1581
  OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.resumePathPrefix,
1606
1582
  OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.routeReference,
1583
+ OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.resumable,
1607
1584
  OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.polling,
1608
1585
  OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.pollIntervalMs,
1609
- OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.decodeLinkUrl
1586
+ OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.decodeLinkUrl,
1587
+ OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.assetBaseUrl
1610
1588
  ];
1611
1589
  }
1612
1590
  connectedCallback() {
@@ -1621,7 +1599,7 @@ function defineElements(options = {}) {
1621
1599
  attributeChangedCallback(name, oldValue, newValue) {
1622
1600
  if (!this.isConnected || this.session.applyingOwnAttributes) return;
1623
1601
  if (oldValue === newValue) return;
1624
- const displayOnly = name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.theme || name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.syncUrl || name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.resumePathPrefix || name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.routeReference || name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.decodeLinkUrl;
1602
+ const displayOnly = name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.theme || name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.syncUrl || name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.resumePathPrefix || name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.routeReference || name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.resumable || name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.decodeLinkUrl || name === OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.assetBaseUrl;
1625
1603
  if (displayOnly) {
1626
1604
  this.render();
1627
1605
  this.syncThemeAncestorObserver();
@@ -1654,6 +1632,27 @@ function defineElements(options = {}) {
1654
1632
  this.stopCheckoutController();
1655
1633
  this.stopThemeAncestorObserver();
1656
1634
  }
1635
+ /**
1636
+ * The asset seam for this element. `resolveAssetUrl` on `defineElements` is
1637
+ * the explicit, programmatic answer and wins; `asset-base-url` is the string
1638
+ * every host can reach — plain markup and the Vue/Svelte/Angular wrappers
1639
+ * included, since `defineElements` is first-write-wins and all three call it
1640
+ * with no options. Setting both is a host mistake worth naming once.
1641
+ */
1642
+ resolveAssetUrlResolver() {
1643
+ const base = this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.assetBaseUrl);
1644
+ if (options.resolveAssetUrl !== void 0) {
1645
+ if (base !== null && !warnedAssetSeamConflict) {
1646
+ warnedAssetSeamConflict = true;
1647
+ globalThis.console?.warn(
1648
+ "[openreceive] Both defineElements({ resolveAssetUrl }) and asset-base-url are set; resolveAssetUrl wins. Drop one."
1649
+ );
1650
+ }
1651
+ return options.resolveAssetUrl;
1652
+ }
1653
+ if (base === null || base.trim() === "") return void 0;
1654
+ return createAssetBaseUrlResolver(base);
1655
+ }
1657
1656
  // Create mode: a `reference` is set but no `invoice` snapshot is provided. The element
1658
1657
  // owns the whole lifecycle — it prepares the checkout against
1659
1658
  // `${prefix}/checkouts/prepare` (locking the amount without minting), mints a Lightning
@@ -1692,11 +1691,29 @@ function defineElements(options = {}) {
1692
1691
  }
1693
1692
  return parsed;
1694
1693
  }
1694
+ /**
1695
+ * Whether a payer who closes this tab has a URL to come back to. Only
1696
+ * `sync-url` puts one there — the refund screen's copy has to say the true
1697
+ * thing either way, so this is read rather than assumed.
1698
+ */
1699
+ isResumable() {
1700
+ const declared = parseBooleanAttribute(
1701
+ this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.resumable)
1702
+ );
1703
+ if (declared !== void 0) return declared;
1704
+ const routeReference = this.getAttribute(
1705
+ OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.routeReference
1706
+ );
1707
+ if (routeReference !== null && routeReference.length > 0) return true;
1708
+ return parseBooleanAttribute(
1709
+ this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.syncUrl)
1710
+ ) === true;
1711
+ }
1695
1712
  /** Order data belongs to the host; this only performs optional History API sync. */
1696
1713
  syncResumePath(reference) {
1697
1714
  const syncUrl = parseBooleanAttribute(
1698
1715
  this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.syncUrl)
1699
- );
1716
+ ) === true;
1700
1717
  if (syncUrl) {
1701
1718
  const resumePathPrefix = this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.resumePathPrefix) ?? "/checkout";
1702
1719
  const routeReference = this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.routeReference) ?? void 0;
@@ -1716,7 +1733,7 @@ function defineElements(options = {}) {
1716
1733
  (invoice) => invoice.rail === "swap" && invoice.swap !== void 0
1717
1734
  );
1718
1735
  if (swapInvoice !== void 0) {
1719
- this.startedSwapInvoice = overlaySwapRefundStaging(swapInvoice, this.startedSwapInvoice);
1736
+ this.startedSwapInvoice = swapInvoice;
1720
1737
  }
1721
1738
  const displayKey = checkoutSnapshotDisplayKey(snapshot);
1722
1739
  if (this.lastSnapshotDisplayKey === displayKey) return;
@@ -1753,6 +1770,7 @@ function defineElements(options = {}) {
1753
1770
  const invoice = invoiceAttr ?? "";
1754
1771
  const lightningRequested = !this.isCreateMode() || this.session.lightningRequested || invoice.length > 0;
1755
1772
  const decodeLinkUrl = this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.decodeLinkUrl) ?? void 0;
1773
+ const assetUrlResolver = this.resolveAssetUrlResolver();
1756
1774
  const selectedQuote = this.selectedSwapAsset === null ? void 0 : this.session.swapQuotes[this.selectedSwapAsset];
1757
1775
  const unavailableSwapQuote = selectedQuote?.available === false ? selectedQuote : void 0;
1758
1776
  const { root, inlineStyles } = this.prepareShadowRoot();
@@ -1769,6 +1787,10 @@ function defineElements(options = {}) {
1769
1787
  this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.status)
1770
1788
  ),
1771
1789
  expires_at: readElementExpiresAt(this),
1790
+ // Host-owned order context, straight off the polled snapshot: the
1791
+ // element has no attribute for it because the payer must never be able
1792
+ // to write the copy next to the amount.
1793
+ ...this.latestCheckoutSnapshot?.description === void 0 ? {} : { description: this.latestCheckoutSnapshot.description },
1772
1794
  theme: this.resolveTheme(),
1773
1795
  payment_wizard: parseBooleanAttribute(
1774
1796
  this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.paymentWizard)
@@ -1779,17 +1801,18 @@ function defineElements(options = {}) {
1779
1801
  selectedBitcoinRoute: this.selection.selectedBitcoinRoute,
1780
1802
  swapOptions: this.swapOptions,
1781
1803
  currenciesLoading: !this.swapOptionsLoaded,
1782
- selectedSwapNetworks: this.selectedSwapNetworks,
1804
+ selectedSwapAssetByGroup: this.selectedSwapAssetByGroup,
1783
1805
  selectedPickerKey: this.selectedPickerKey,
1784
1806
  startingSwapAsset: this.session.startingSwapAsset,
1785
1807
  selectedSwapAsset: this.selectedSwapAsset,
1808
+ resumable: this.isResumable(),
1786
1809
  ...this.latestCheckoutSnapshot?.amount_msats === void 0 ? {} : { amountMsats: this.latestCheckoutSnapshot.amount_msats },
1787
1810
  ...this.latestCheckoutSnapshot?.fiat === void 0 ? {} : { fiat: this.latestCheckoutSnapshot.fiat },
1788
1811
  ...this.latestCheckoutSnapshot?.reference === void 0 ? {} : { reference: this.latestCheckoutSnapshot.reference },
1789
1812
  ...this.latestCheckoutSnapshot?.checkout_id === void 0 ? {} : { checkoutId: this.latestCheckoutSnapshot.checkout_id },
1790
1813
  lightningInvoice: invoice,
1791
1814
  ...decodeLinkUrl === void 0 ? {} : { decodeLinkUrl },
1792
- ...options.resolveAssetUrl === void 0 ? {} : { resolveAssetUrl: options.resolveAssetUrl },
1815
+ ...assetUrlResolver === void 0 ? {} : { resolveAssetUrl: assetUrlResolver },
1793
1816
  ...this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.paymentHash) === null ? {} : {
1794
1817
  paymentHash: this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.paymentHash) ?? void 0
1795
1818
  },
@@ -1863,7 +1886,8 @@ function defineElements(options = {}) {
1863
1886
  startCheckoutController() {
1864
1887
  const attributeSnapshot = this.currentCheckoutSnapshot();
1865
1888
  const latest = this.latestCheckoutSnapshot;
1866
- const snapshot = latest?.active?.rail === "swap" ? latest : attributeSnapshot ?? latest;
1889
+ const chosen = latest?.active?.rail === "swap" ? latest : attributeSnapshot ?? latest;
1890
+ const snapshot = chosen !== void 0 && chosen.description === void 0 && latest?.description !== void 0 && latest.reference === chosen.reference ? { ...chosen, description: latest.description } : chosen;
1867
1891
  const prefix = this.resolvePollPrefix(snapshot?.reference);
1868
1892
  if (snapshot === void 0) {
1869
1893
  this.stopCheckoutController();
@@ -1879,7 +1903,8 @@ function defineElements(options = {}) {
1879
1903
  this.stopCheckoutController();
1880
1904
  this.controller = createCheckoutController({
1881
1905
  snapshot,
1882
- ...prefix === void 0 || !polling ? {} : { prefix },
1906
+ ...prefix === void 0 ? {} : { prefix },
1907
+ polling,
1883
1908
  ...pollIntervalMs === void 0 ? {} : { pollIntervalMs },
1884
1909
  logger: options.logger,
1885
1910
  onError: (error) => this.dispatchError(error),
@@ -1974,7 +1999,7 @@ function defineElements(options = {}) {
1974
1999
  }
1975
2000
  const swapInvoice = this.currentSwapInvoice();
1976
2001
  if (swapInvoice !== void 0) {
1977
- const display = createSwapDisplayModel2(swapInvoice, {});
2002
+ const display = createSwapDisplayModel2(swapInvoice, { resumable: this.isResumable() });
1978
2003
  const swapCountdown = root.querySelector('[part="swap-countdown"]');
1979
2004
  if (swapCountdown !== null && display !== void 0) {
1980
2005
  swapCountdown.textContent = display.countdownLabel;
@@ -2009,9 +2034,15 @@ function defineElements(options = {}) {
2009
2034
  if (!(button instanceof HTMLButtonElement) || button.disabled) return;
2010
2035
  const key = button.getAttribute(OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES7.pickerSelect);
2011
2036
  if (key === null || key.length === 0) return;
2012
- const methodPick = parseMethodPickerKey(key);
2013
- if (methodPick !== null) {
2014
- const method = parsePaymentMethod(methodPick.methodId);
2037
+ const selection = resolveWizardSelection({
2038
+ pickerKey: key,
2039
+ previousKey: this.selectedPickerKey,
2040
+ entries: buildMethodGridEntries2(paymentMethods2, this.swapOptions),
2041
+ selectedAssetByGroup: this.selectedSwapAssetByGroup
2042
+ });
2043
+ if (selection.kind === "none") return;
2044
+ if (selection.kind === "select_method") {
2045
+ const method = parsePaymentMethod(selection.methodId);
2015
2046
  if (method === null) return;
2016
2047
  this.selectedPickerKey = null;
2017
2048
  this.selection = updatePaymentWizardSelection(this.selection, {
@@ -2024,23 +2055,13 @@ function defineElements(options = {}) {
2024
2055
  this.render();
2025
2056
  return;
2026
2057
  }
2027
- const previousKey = this.selectedPickerKey;
2028
- this.selectedPickerKey = key;
2029
- const entries = buildMethodGridEntries2(paymentMethods2, this.swapOptions);
2030
- const nextGroup = findSwapGridGroup2(entries, key);
2031
- if (nextGroup !== void 0 && nextGroup.options.length === 1) {
2032
- const option = nextGroup.options.find((entry) => entry.available !== false) ?? nextGroup.options[0];
2033
- if (option === void 0 || option.available === false) return;
2058
+ if (selection.kind === "start_swap") {
2034
2059
  this.selectedPickerKey = null;
2035
- void this.session.startSwap(option.pay_in_asset);
2060
+ void this.session.startSwap(selection.payInAsset);
2036
2061
  return;
2037
2062
  }
2038
- this.selectedSwapNetworks = updateSelectedSwapNetworks({
2039
- entries,
2040
- nextKey: key,
2041
- previousKey,
2042
- selectedNetworks: this.selectedSwapNetworks
2043
- });
2063
+ this.selectedPickerKey = key;
2064
+ this.selectedSwapAssetByGroup = selection.selectedAssetByGroup;
2044
2065
  this.render();
2045
2066
  });
2046
2067
  });
@@ -2070,7 +2091,7 @@ function defineElements(options = {}) {
2070
2091
  if (target === "swap-asset") {
2071
2092
  this.selectedSwapAsset = null;
2072
2093
  this.selectedPickerKey = null;
2073
- this.selectedSwapNetworks = {};
2094
+ this.selectedSwapAssetByGroup = {};
2074
2095
  this.render();
2075
2096
  return;
2076
2097
  }
@@ -2119,8 +2140,8 @@ function defineElements(options = {}) {
2119
2140
  if (groupKey === null || groupKey.length === 0 || payInAsset === null) return;
2120
2141
  const details = button.closest("details");
2121
2142
  if (details instanceof HTMLDetailsElement) details.open = false;
2122
- this.selectedSwapNetworks = {
2123
- ...this.selectedSwapNetworks,
2143
+ this.selectedSwapAssetByGroup = {
2144
+ ...this.selectedSwapAssetByGroup,
2124
2145
  [groupKey]: payInAsset
2125
2146
  };
2126
2147
  this.render();
@@ -2131,8 +2152,9 @@ function defineElements(options = {}) {
2131
2152
  this.dismissedSwapInvoiceId = current?.invoice_id ?? null;
2132
2153
  this.selectedSwapAsset = null;
2133
2154
  this.selectedPickerKey = null;
2134
- this.selectedSwapNetworks = {};
2155
+ this.selectedSwapAssetByGroup = {};
2135
2156
  this.clearRefundAddressDraft();
2157
+ this.controller?.clearSwapRefundStaging();
2136
2158
  void this.session.ensureLightning();
2137
2159
  this.render();
2138
2160
  });
@@ -2271,20 +2293,10 @@ function defineElements(options = {}) {
2271
2293
  }
2272
2294
  }
2273
2295
  async refundSwap(attemptId, refundAddress, confirm) {
2274
- const reference = this.getAttribute(OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES3.reference);
2275
- const prefix = this.resolvePollPrefix(reference ?? void 0);
2276
- if (prefix === void 0) return;
2296
+ const controller = this.controller;
2297
+ if (controller === void 0) return;
2277
2298
  try {
2278
- this.startedSwapInvoice = await requestSwapRefund({
2279
- fetch: globalThis.fetch,
2280
- prefix,
2281
- ...reference === null ? {} : { reference },
2282
- invoices: [this.startedSwapInvoice, ...this.latestCheckoutSnapshot?.invoices ?? []],
2283
- attemptId,
2284
- refundAddress,
2285
- confirm,
2286
- logger: options.logger
2287
- });
2299
+ this.startedSwapInvoice = confirm ? await controller.confirmSwapRefund({ attemptId, refundAddress }) : await controller.stageSwapRefund({ attemptId, refundAddress });
2288
2300
  this.dismissedSwapInvoiceId = null;
2289
2301
  this.render();
2290
2302
  } catch (error) {
@@ -2292,16 +2304,10 @@ function defineElements(options = {}) {
2292
2304
  }
2293
2305
  }
2294
2306
  currentSwapInvoice() {
2295
- const fromSnapshot = this.latestCheckoutSnapshot?.invoices.find(
2296
- (invoice) => invoice.rail === "swap" && invoice.swap !== void 0 && invoice.invoice_id !== this.dismissedSwapInvoiceId
2297
- );
2298
- if (this.startedSwapInvoice === void 0 || this.startedSwapInvoice.invoice_id === this.dismissedSwapInvoiceId) {
2299
- return fromSnapshot;
2300
- }
2301
- const matched = this.latestCheckoutSnapshot?.invoices.find(
2302
- (invoice) => invoice.invoice_id === this.startedSwapInvoice?.invoice_id
2303
- ) ?? this.startedSwapInvoice;
2304
- return overlaySwapRefundStaging(matched, this.startedSwapInvoice);
2307
+ return selectCurrentSwapInvoice(this.latestCheckoutSnapshot, {
2308
+ started: this.startedSwapInvoice,
2309
+ dismissedInvoiceId: this.dismissedSwapInvoiceId
2310
+ });
2305
2311
  }
2306
2312
  renderSwapQrCodes(root) {
2307
2313
  root.querySelectorAll(OPENRECEIVE_PAYMENT_WIZARD_SELECTORS3.swapQr).forEach((target) => {