@nymbalhq/web-components 1.1.0 → 1.2.0

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.
Files changed (187) hide show
  1. package/astro/NymbalElement.astro +19 -0
  2. package/dist/base-element-CPMgAdyQ.d.ts +91 -0
  3. package/dist/chunk-2TPQNQYO.js +155 -0
  4. package/dist/chunk-2TPQNQYO.js.map +1 -0
  5. package/dist/chunk-2XXBQPDB.js +113 -0
  6. package/dist/chunk-2XXBQPDB.js.map +1 -0
  7. package/dist/chunk-42SSME5H.js +84 -0
  8. package/dist/chunk-42SSME5H.js.map +1 -0
  9. package/dist/chunk-4COVK727.js +204 -0
  10. package/dist/chunk-4COVK727.js.map +1 -0
  11. package/dist/chunk-5VWZ2U5K.js +149 -0
  12. package/dist/chunk-5VWZ2U5K.js.map +1 -0
  13. package/dist/chunk-6XCCAAMF.js +656 -0
  14. package/dist/chunk-6XCCAAMF.js.map +1 -0
  15. package/dist/chunk-7JAAOVCE.js +114 -0
  16. package/dist/chunk-7JAAOVCE.js.map +1 -0
  17. package/dist/chunk-B73QVSA5.js +206 -0
  18. package/dist/chunk-B73QVSA5.js.map +1 -0
  19. package/dist/chunk-DR5QB5VN.js +17 -0
  20. package/dist/chunk-DR5QB5VN.js.map +1 -0
  21. package/dist/chunk-DXWMNB5F.js +70 -0
  22. package/dist/chunk-DXWMNB5F.js.map +1 -0
  23. package/dist/chunk-EBMA7JGV.js +130 -0
  24. package/dist/chunk-EBMA7JGV.js.map +1 -0
  25. package/dist/chunk-EYDMJNNK.js +176 -0
  26. package/dist/chunk-EYDMJNNK.js.map +1 -0
  27. package/dist/chunk-FIF6GQ6U.js +141 -0
  28. package/dist/chunk-FIF6GQ6U.js.map +1 -0
  29. package/dist/chunk-GHXTJ2CF.js +109 -0
  30. package/dist/chunk-GHXTJ2CF.js.map +1 -0
  31. package/dist/chunk-H3ZBYNHV.js +153 -0
  32. package/dist/chunk-H3ZBYNHV.js.map +1 -0
  33. package/dist/chunk-HPBC3OG7.js +174 -0
  34. package/dist/chunk-HPBC3OG7.js.map +1 -0
  35. package/dist/chunk-IYT7VXFS.js +105 -0
  36. package/dist/chunk-IYT7VXFS.js.map +1 -0
  37. package/dist/chunk-LHYXNQWY.js +105 -0
  38. package/dist/chunk-LHYXNQWY.js.map +1 -0
  39. package/dist/chunk-LO2EW5YP.js +25 -0
  40. package/dist/chunk-LO2EW5YP.js.map +1 -0
  41. package/dist/chunk-LSQPUZK6.js +291 -0
  42. package/dist/chunk-LSQPUZK6.js.map +1 -0
  43. package/dist/chunk-N4Y5DI4X.js +82 -0
  44. package/dist/chunk-N4Y5DI4X.js.map +1 -0
  45. package/dist/chunk-NE6UHTOT.js +148 -0
  46. package/dist/chunk-NE6UHTOT.js.map +1 -0
  47. package/dist/chunk-NW47OJVN.js +138 -0
  48. package/dist/chunk-NW47OJVN.js.map +1 -0
  49. package/dist/chunk-O2YKCDR2.js +77 -0
  50. package/dist/chunk-O2YKCDR2.js.map +1 -0
  51. package/dist/chunk-PN6QKYE4.js +71 -0
  52. package/dist/chunk-PN6QKYE4.js.map +1 -0
  53. package/dist/chunk-RAOGZH3Z.js +188 -0
  54. package/dist/chunk-RAOGZH3Z.js.map +1 -0
  55. package/dist/chunk-RVSKX2RX.js +11 -0
  56. package/dist/chunk-RVSKX2RX.js.map +1 -0
  57. package/dist/chunk-RZVFA3N5.js +154 -0
  58. package/dist/chunk-RZVFA3N5.js.map +1 -0
  59. package/dist/chunk-SM7TASEW.js +135 -0
  60. package/dist/chunk-SM7TASEW.js.map +1 -0
  61. package/dist/chunk-U2PU4CWB.js +124 -0
  62. package/dist/chunk-U2PU4CWB.js.map +1 -0
  63. package/dist/chunk-UCEVCLQ7.js +5 -0
  64. package/dist/chunk-UCEVCLQ7.js.map +1 -0
  65. package/dist/chunk-UYSZOEOA.js +32 -0
  66. package/dist/chunk-UYSZOEOA.js.map +1 -0
  67. package/dist/chunk-V55K2CS5.js +126 -0
  68. package/dist/chunk-V55K2CS5.js.map +1 -0
  69. package/dist/chunk-VTQVGNVC.js +346 -0
  70. package/dist/chunk-VTQVGNVC.js.map +1 -0
  71. package/dist/chunk-W23P7SI4.js +239 -0
  72. package/dist/chunk-W23P7SI4.js.map +1 -0
  73. package/dist/chunk-W7D3STJH.js +279 -0
  74. package/dist/chunk-W7D3STJH.js.map +1 -0
  75. package/dist/chunk-WEEHXWM3.js +172 -0
  76. package/dist/chunk-WEEHXWM3.js.map +1 -0
  77. package/dist/chunk-WTEVTCIL.js +128 -0
  78. package/dist/chunk-WTEVTCIL.js.map +1 -0
  79. package/dist/chunk-XE6IWISA.js +137 -0
  80. package/dist/chunk-XE6IWISA.js.map +1 -0
  81. package/dist/elements/nymbal-add-to-cart.d.ts +14 -0
  82. package/dist/elements/nymbal-add-to-cart.js +10 -0
  83. package/dist/elements/nymbal-add-to-cart.js.map +1 -0
  84. package/dist/elements/nymbal-address-book.d.ts +30 -0
  85. package/dist/elements/nymbal-address-book.js +13 -0
  86. package/dist/elements/nymbal-address-book.js.map +1 -0
  87. package/dist/elements/nymbal-address-form.d.ts +29 -0
  88. package/dist/elements/nymbal-address-form.js +12 -0
  89. package/dist/elements/nymbal-address-form.js.map +1 -0
  90. package/dist/elements/nymbal-breadcrumb.d.ts +15 -0
  91. package/dist/elements/nymbal-breadcrumb.js +11 -0
  92. package/dist/elements/nymbal-breadcrumb.js.map +1 -0
  93. package/dist/elements/nymbal-cart-drawer.d.ts +24 -0
  94. package/dist/elements/nymbal-cart-drawer.js +11 -0
  95. package/dist/elements/nymbal-cart-drawer.js.map +1 -0
  96. package/dist/elements/nymbal-cart-page.d.ts +20 -0
  97. package/dist/elements/nymbal-cart-page.js +14 -0
  98. package/dist/elements/nymbal-cart-page.js.map +1 -0
  99. package/dist/elements/nymbal-category-nav.d.ts +15 -0
  100. package/dist/elements/nymbal-category-nav.js +11 -0
  101. package/dist/elements/nymbal-category-nav.js.map +1 -0
  102. package/dist/elements/nymbal-checkout.d.ts +77 -0
  103. package/dist/elements/nymbal-checkout.js +15 -0
  104. package/dist/elements/nymbal-checkout.js.map +1 -0
  105. package/dist/elements/nymbal-image-gallery.d.ts +19 -0
  106. package/dist/elements/nymbal-image-gallery.js +10 -0
  107. package/dist/elements/nymbal-image-gallery.js.map +1 -0
  108. package/dist/elements/nymbal-login-form.d.ts +17 -0
  109. package/dist/elements/nymbal-login-form.js +12 -0
  110. package/dist/elements/nymbal-login-form.js.map +1 -0
  111. package/dist/elements/nymbal-mini-cart.d.ts +17 -0
  112. package/dist/elements/nymbal-mini-cart.js +10 -0
  113. package/dist/elements/nymbal-mini-cart.js.map +1 -0
  114. package/dist/elements/nymbal-order-summary.d.ts +20 -0
  115. package/dist/elements/nymbal-order-summary.js +11 -0
  116. package/dist/elements/nymbal-order-summary.js.map +1 -0
  117. package/dist/elements/nymbal-payment-element.d.ts +4 -0
  118. package/dist/elements/nymbal-payment-element.js +10 -0
  119. package/dist/elements/nymbal-payment-element.js.map +1 -0
  120. package/dist/elements/nymbal-product-card.d.ts +4 -0
  121. package/dist/elements/nymbal-product-card.js +12 -0
  122. package/dist/elements/nymbal-product-card.js.map +1 -0
  123. package/dist/elements/nymbal-product-filter.d.ts +32 -0
  124. package/dist/elements/nymbal-product-filter.js +10 -0
  125. package/dist/elements/nymbal-product-filter.js.map +1 -0
  126. package/dist/elements/nymbal-product-grid.d.ts +24 -0
  127. package/dist/elements/nymbal-product-grid.js +14 -0
  128. package/dist/elements/nymbal-product-grid.js.map +1 -0
  129. package/dist/elements/nymbal-product-price.d.ts +18 -0
  130. package/dist/elements/nymbal-product-price.js +12 -0
  131. package/dist/elements/nymbal-product-price.js.map +1 -0
  132. package/dist/elements/nymbal-profile-form.d.ts +19 -0
  133. package/dist/elements/nymbal-profile-form.js +12 -0
  134. package/dist/elements/nymbal-profile-form.js.map +1 -0
  135. package/dist/elements/nymbal-quantity-selector.d.ts +20 -0
  136. package/dist/elements/nymbal-quantity-selector.js +10 -0
  137. package/dist/elements/nymbal-quantity-selector.js.map +1 -0
  138. package/dist/elements/nymbal-register-form.d.ts +17 -0
  139. package/dist/elements/nymbal-register-form.js +12 -0
  140. package/dist/elements/nymbal-register-form.js.map +1 -0
  141. package/dist/elements/nymbal-review-form.d.ts +17 -0
  142. package/dist/elements/nymbal-review-form.js +12 -0
  143. package/dist/elements/nymbal-review-form.js.map +1 -0
  144. package/dist/elements/nymbal-review-list.d.ts +14 -0
  145. package/dist/elements/nymbal-review-list.js +10 -0
  146. package/dist/elements/nymbal-review-list.js.map +1 -0
  147. package/dist/elements/nymbal-search-bar.d.ts +19 -0
  148. package/dist/elements/nymbal-search-bar.js +10 -0
  149. package/dist/elements/nymbal-search-bar.js.map +1 -0
  150. package/dist/elements/nymbal-search-results.d.ts +29 -0
  151. package/dist/elements/nymbal-search-results.js +14 -0
  152. package/dist/elements/nymbal-search-results.js.map +1 -0
  153. package/dist/elements/nymbal-site-footer.d.ts +15 -0
  154. package/dist/elements/nymbal-site-footer.js +11 -0
  155. package/dist/elements/nymbal-site-footer.js.map +1 -0
  156. package/dist/elements/nymbal-site-header.d.ts +19 -0
  157. package/dist/elements/nymbal-site-header.js +11 -0
  158. package/dist/elements/nymbal-site-header.js.map +1 -0
  159. package/dist/elements/nymbal-sort-dropdown.d.ts +28 -0
  160. package/dist/elements/nymbal-sort-dropdown.js +10 -0
  161. package/dist/elements/nymbal-sort-dropdown.js.map +1 -0
  162. package/dist/elements/nymbal-stock-badge.d.ts +16 -0
  163. package/dist/elements/nymbal-stock-badge.js +11 -0
  164. package/dist/elements/nymbal-stock-badge.js.map +1 -0
  165. package/dist/elements/nymbal-toast.d.ts +18 -0
  166. package/dist/elements/nymbal-toast.js +10 -0
  167. package/dist/elements/nymbal-toast.js.map +1 -0
  168. package/dist/elements/nymbal-variant-selector.d.ts +16 -0
  169. package/dist/elements/nymbal-variant-selector.js +10 -0
  170. package/dist/elements/nymbal-variant-selector.js.map +1 -0
  171. package/dist/form-element-C-qo3gpD.d.ts +25 -0
  172. package/dist/hydrate-support.d.ts +2 -0
  173. package/dist/hydrate-support.js +3 -0
  174. package/dist/hydrate-support.js.map +1 -0
  175. package/dist/index.d.ts +38 -157
  176. package/dist/index.js +127 -1407
  177. package/dist/index.js.map +1 -1
  178. package/dist/nymbal-payment-element-Bt8EF42T.d.ts +51 -0
  179. package/dist/nymbal-product-card-C8HkE_rG.d.ts +47 -0
  180. package/dist/register-components.d.ts +46 -0
  181. package/dist/register-components.js +48 -0
  182. package/dist/register-components.js.map +1 -0
  183. package/dist/selected-variant-Dym0shlJ.d.ts +23 -0
  184. package/dist/ssr/index.d.ts +30 -0
  185. package/dist/ssr/index.js +143 -0
  186. package/dist/ssr/index.js.map +1 -0
  187. package/package.json +33 -6
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/checkout.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport type { PaymentConfirmation, PaymentConfirmContext } from '@nymbalhq/sdk'\nimport type { Address, CartItem } from '@nymbalhq/types'\nimport { baseStyles, NymbalElement, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { renderField } from '../form-element.js'\nimport { formatPrice } from '../format.js'\nimport { shownCart } from '../cart-projection.js'\nimport { buttonStyles, fieldStyles } from '../view-styles.js'\nimport { NymbalAddressForm } from './address-form.js'\nimport type { NymbalPaymentElement } from './payment-element.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-checkout',\n reactName: 'Checkout',\n // The cart a server read (cart-projection.ts), shown in the summary until the cart store settles.\n attributes: [{ name: 'cart', prop: 'cart', type: 'json' }],\n events: [\n {\n name: 'nymbal:checkout:placed',\n prop: 'onPlaced',\n detail: '{ orderNumber: string }',\n },\n ],\n parts: [\n 'layout',\n 'main',\n 'title',\n 'steps',\n 'step',\n 'step-active',\n 'step-done',\n 'step-divider',\n 'step-divider-done',\n 'error',\n 'section',\n 'form',\n 'section-title',\n 'field',\n 'label',\n 'input',\n 'billing-same',\n 'billing',\n 'billing-title',\n 'payment',\n 'actions',\n 'back',\n 'submit',\n 'terms',\n 'summary',\n 'summary-title',\n 'items',\n 'item',\n 'item-name',\n 'item-variant',\n 'item-qty',\n 'item-price',\n 'summary-row',\n 'subtotal',\n 'total',\n ],\n slots: [\n 'payment',\n 'title',\n 'contact-continue-label',\n 'shipping-continue-label',\n 'place-order-label',\n 'terms',\n 'summary-title',\n ],\n children: true,\n}\n\nconst STEPS = ['contact', 'shipping', 'payment'] as const\ntype Step = (typeof STEPS)[number]\nconst STEP_LABELS: Record<Step, string> = {\n contact: 'Contact',\n shipping: 'Shipping',\n payment: 'Payment',\n}\n\n/** Outlasts the 500 ms an OS allows between the two clicks of a double click. */\nconst STEP_HEIGHT_HOLD_MS = 600\nconst PAYMENT_FAILED = 'Payment failed. Please try again.'\n\n/** No payment element: nothing to confirm, as for a payments adapter with no client half. */\nconst NO_PAYMENT_STEP: PaymentConfirmation = { ok: true, confirmed: false }\n\n/**\n * The checkout: contact, shipping, then payment with billing, beside the order summary. It drives the SDK's checkout\n * store, and on an order dispatches `nymbal:checkout:placed`; the page navigates. The payment element is the page's\n * light-DOM child in the `payment` slot, so the payment driver's UI shares the document tree and is mounted once.\n */\nexport class NymbalCheckout extends NymbalElement {\n static override schema = SCHEMA\n static uses = [NymbalAddressForm]\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n buttonStyles,\n fieldStyles,\n css`\n [hidden] {\n display: none !important;\n }\n [part~='layout'] {\n display: grid;\n grid-template-columns: 1fr;\n gap: var(--nymbal-spacing-xl, 2rem);\n }\n @media (min-width: 769px) {\n [part~='layout'] {\n grid-template-columns: 1fr 380px;\n align-items: start;\n }\n }\n [part~='title'] {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n font-size: 1.75rem;\n font-weight: var(--nymbal-font-weight-bold, 700);\n }\n [part~='steps'] {\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: var(--nymbal-spacing-xl, 2rem);\n padding: var(--nymbal-spacing-md, 1rem) 0;\n counter-reset: step;\n }\n [part~='step'] {\n display: flex;\n align-items: center;\n gap: var(--nymbal-spacing-sm, 0.5rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text-muted, #71717a);\n white-space: nowrap;\n }\n [part~='step']::before {\n counter-increment: step;\n content: counter(step);\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex-shrink: 0;\n border: 2px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-full, 9999px);\n font-size: var(--nymbal-font-size-xs, 0.75rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n }\n [part~='step-active'] {\n color: var(--nymbal-color-primary, #18181b);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='step-active']::before {\n background-color: var(--nymbal-color-primary, #18181b);\n border-color: var(--nymbal-color-primary, #18181b);\n color: var(--nymbal-color-text-inverse, #fff);\n }\n [part~='step-done']::before {\n content: '\\\\2713';\n background-color: var(--nymbal-color-success, #16a34a);\n border-color: var(--nymbal-color-success, #16a34a);\n color: var(--nymbal-color-text-inverse, #fff);\n }\n [part~='step-divider'] {\n width: 40px;\n height: 2px;\n flex-shrink: 0;\n margin: 0 var(--nymbal-spacing-sm, 0.5rem);\n background-color: var(--nymbal-color-border, #e4e4e7);\n }\n [part~='step-divider-done'] {\n background-color: var(--nymbal-color-success, #16a34a);\n }\n @media (max-width: 640px) {\n [part~='step'] span {\n display: none;\n }\n [part~='step-divider'] {\n width: 24px;\n }\n }\n [part~='section-title'] {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-lg, 1.125rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n color: var(--nymbal-color-primary, #18181b);\n }\n [part~='billing-same'] {\n display: flex;\n align-items: center;\n gap: var(--nymbal-spacing-sm, 0.5rem);\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n cursor: pointer;\n }\n [part~='billing-title'] {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-base, 1rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n }\n [part~='payment'] {\n margin-top: var(--nymbal-spacing-md, 1rem);\n }\n [part~='actions'] {\n display: flex;\n justify-content: space-between;\n gap: var(--nymbal-spacing-sm, 0.5rem);\n margin-top: var(--nymbal-spacing-lg, 1.5rem);\n }\n [part~='submit'] {\n flex: 1;\n padding: var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-base, 1rem);\n }\n [part~='terms'] {\n margin: var(--nymbal-spacing-sm, 0.5rem) 0 0;\n font-size: var(--nymbal-font-size-xs, 0.75rem);\n color: var(--nymbal-color-text-muted, #71717a);\n text-align: center;\n }\n [part~='summary'] {\n position: sticky;\n top: 80px;\n padding: var(--nymbal-spacing-lg, 1.5rem);\n background-color: #fafafa;\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-lg, 0.75rem);\n }\n [part~='summary-title'] {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n padding-bottom: var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-lg, 1.125rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n color: var(--nymbal-color-primary, #18181b);\n border-bottom: 1px solid var(--nymbal-color-border, #e4e4e7);\n }\n [part~='items'] {\n display: flex;\n flex-direction: column;\n gap: var(--nymbal-spacing-sm, 0.5rem);\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n }\n [part~='item'] {\n display: flex;\n justify-content: space-between;\n align-items: start;\n gap: var(--nymbal-spacing-sm, 0.5rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n }\n [part~='item-name'] {\n flex: 1;\n min-width: 0;\n }\n [part~='item-variant'],\n [part~='item-qty'] {\n flex-shrink: 0;\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='item-price'] {\n flex-shrink: 0;\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n dl {\n margin: 0;\n }\n dd {\n margin: 0;\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='summary-row'] {\n display: flex;\n justify-content: space-between;\n padding: var(--nymbal-spacing-xs, 0.25rem) 0;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n }\n [part~='summary-row'] dt {\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='total'] {\n display: flex;\n justify-content: space-between;\n margin-top: var(--nymbal-spacing-sm, 0.5rem);\n padding-top: var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-lg, 1.125rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n color: var(--nymbal-color-primary, #18181b);\n border-top: 1px solid var(--nymbal-color-border, #e4e4e7);\n }\n `,\n ]\n\n private step: Step = 'contact'\n private error: string | null = null\n private billingSame = true\n private placing = false\n private placed = false\n /** True from a step change until the next animation frame (Decision 4's multi-click rule). */\n private stepChanging = false\n private stepTurn = 0\n private heightRelease: ReturnType<typeof setTimeout> | undefined\n\n declare cart: unknown\n\n protected override stores() {\n return [this.client.cart]\n }\n\n protected override storeSettled(): void {\n this.cart = undefined\n }\n\n override connectedCallback(): void {\n super.connectedCallback()\n this.addEventListener('nymbal:payment:status', this.onPaymentStatus)\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n this.removeEventListener('nymbal:payment:status', this.onPaymentStatus)\n this.releaseMainHeight()\n }\n\n private readonly onPaymentStatus = (): void => this.requestUpdate()\n\n private paymentElement(): NymbalPaymentElement | null {\n return this.querySelector<NymbalPaymentElement>('nymbal-payment-element')\n }\n\n /**\n * Read at each render, so a status the child reached during its own upgrade, before this listened, still holds.\n * Without a client (a server render) there is no light DOM to read, and no order to place.\n */\n private paymentBlocked(): boolean {\n return this.readsStores && this.paymentElement()?.status === 'error'\n }\n\n private addressForm(prefix: 'shipping' | 'billing'): NymbalAddressForm | null {\n return this.renderRoot.querySelector<NymbalAddressForm>(\n `nymbal-address-form[id-prefix=\"${prefix}\"]`,\n )\n }\n\n private go(step: Step): void {\n this.holdMainHeight()\n this.step = step\n this.error = null\n this.stepChanging = true\n const turn = ++this.stepTurn\n requestAnimationFrame(() => {\n if (turn === this.stepTurn) this.stepChanging = false\n })\n this.requestUpdate()\n }\n\n /**\n * Keeps the main column at least as tall as the step it replaces until a double click is over. A shorter next step\n * would shorten the page under a still cursor, and the second click would land on whatever rose there (a footer\n * link), outside this element, where neither multi-click guard sees it. Released after 600 ms, past the 500 ms an OS\n * allows between the two clicks of a double click, so the column then settles to the shown step's own height.\n */\n private holdMainHeight(): void {\n const main = this.renderRoot.querySelector<HTMLElement>(\"[part~='main']\")\n if (main === null) return\n const height = main.offsetHeight\n if (height > (Number.parseFloat(main.style.minHeight) || 0))\n main.style.minHeight = `${height}px`\n clearTimeout(this.heightRelease)\n this.heightRelease = setTimeout(() => this.releaseMainHeight(), STEP_HEIGHT_HOLD_MS)\n }\n\n private releaseMainHeight(): void {\n clearTimeout(this.heightRelease)\n this.heightRelease = undefined\n const main = this.renderRoot.querySelector<HTMLElement>(\"[part~='main']\")\n if (main !== null) main.style.minHeight = ''\n }\n\n private showError(message: string): void {\n this.error = message\n this.requestUpdate()\n }\n\n /**\n * Whether a submit of `step`'s form goes ahead. The step setters are synchronous, so by a double click's second\n * click the next step has rendered under the cursor: a submit in the frame of a step change is not the shopper's.\n */\n private accepts(event: Event, step: Step): boolean {\n event.preventDefault()\n return this.step === step && !this.stepChanging\n }\n\n /** The second click of a multi-click is not a decision to continue or to pay; the first one already submitted. */\n private readonly onSubmitClick = (event: MouseEvent): void => {\n if (event.detail > 1) event.preventDefault()\n }\n\n private readonly onContact = (event: Event): void => {\n if (!this.accepts(event, 'contact')) return\n const email =\n this.renderRoot.querySelector<HTMLInputElement>('#contact-email')?.value.trim() ?? ''\n if (email === '') return this.showError('Please enter your email address.')\n this.client.checkout.setEmail(email)\n this.go('shipping')\n }\n\n private readonly onShipping = (event: Event): void => {\n if (!this.accepts(event, 'shipping')) return\n const form = this.addressForm('shipping')\n if (form === null || !form.reportValidity()) return\n this.client.checkout.setShippingAddress(form.value)\n this.client.checkout.setShippingMethod({\n carrier: 'Standard',\n service: 'Standard Shipping',\n amountMinor: 0,\n currency: this.client.cart.getState().currency || 'GBP',\n estimatedDays: 5,\n })\n this.go('payment')\n }\n\n private billingAddress(): Address | null {\n if (this.billingSame) return this.client.checkout.getState().shippingAddress\n const form = this.addressForm('billing')\n if (form === null || !form.reportValidity()) return null\n return form.value\n }\n\n private readonly onPlace = (event: Event): void => {\n if (!this.accepts(event, 'payment')) return\n if (this.placing || this.placed || this.paymentBlocked()) return\n const billing = this.billingAddress()\n if (billing === null) return\n this.placing = true\n this.error = null\n this.requestUpdate()\n void this.place(billing)\n }\n\n /** Calls the SDK before its first `await`, so the in-flight flag set by the caller covers the order request. */\n private async place(billing: Address): Promise<void> {\n const checkout = this.client.checkout\n try {\n checkout.setBillingAddress(billing)\n const result = await checkout.submitPayment()\n if (result === null) {\n // A resubmit of a cart that already became an order (Back, then Place Order) goes to that order.\n const { placedOrderNumber, error } = checkout.getState()\n if (placedOrderNumber) return this.leaveFor(placedOrderNumber)\n return this.stay(error ?? PAYMENT_FAILED)\n }\n if (result.payment.status === 'failed') {\n return this.stay(checkout.getState().error ?? PAYMENT_FAILED)\n }\n // Confirmed only when the driver says the payment went through; otherwise the order shows it awaiting payment.\n if (result.payment.status === 'requires_action') {\n const { email, billingAddress } = checkout.getState()\n const confirmation = await this.confirm(result.paymentIntent.clientSecret, {\n ...(email !== '' && { email }),\n ...(billingAddress !== null && { billing: billingAddress }),\n })\n if (!confirmation.ok) return this.stay(confirmation.error)\n if (confirmation.confirmed) checkout.markConfirmed()\n }\n this.leaveFor(result.orderNumber)\n } catch (err) {\n this.stay(err instanceof Error && err.message !== '' ? err.message : PAYMENT_FAILED)\n }\n }\n\n private confirm(\n clientSecret: string,\n context: PaymentConfirmContext,\n ): Promise<PaymentConfirmation> {\n return this.paymentElement()?.confirm(clientSecret, context) ?? Promise.resolve(NO_PAYMENT_STEP)\n }\n\n private stay(message: string): void {\n this.placing = false\n this.showError(message)\n }\n\n /** Place Order stays disabled from here through the page's navigation. */\n private leaveFor(orderNumber: string): void {\n this.client.checkout.reset()\n this.placing = false\n this.placed = true\n this.requestUpdate()\n this.dispatchEvent(\n new CustomEvent('nymbal:checkout:placed', {\n bubbles: true,\n composed: true,\n detail: { orderNumber },\n }),\n )\n }\n\n private readonly onBillingSame = (event: Event): void => {\n this.billingSame = (event.target as HTMLInputElement).checked\n this.requestUpdate()\n }\n\n private back(step: Step): () => void {\n return () => {\n if (!this.placing && !this.placed) this.go(step)\n }\n }\n\n private renderSteps() {\n const current = STEPS.indexOf(this.step)\n return html`\n <div part=\"steps\" data-testid=\"checkout-steps\">\n ${STEPS.map((step, index) => {\n const state = index < current ? ' step-done' : index === current ? ' step-active' : ''\n return html`\n ${index > 0\n ? html`<div\n part=\"step-divider${index <= current ? ' step-divider-done' : ''}\"\n aria-hidden=\"true\"\n ></div>`\n : nothing}\n <div\n part=\"step${state}\"\n data-testid=\"checkout-step-${step}\"\n aria-current=${index === current ? 'step' : nothing}\n >\n <span>${STEP_LABELS[step]}</span>\n </div>\n `\n })}\n </div>\n `\n }\n\n private renderContact() {\n return html`\n <section part=\"section\" data-testid=\"checkout-contact\" ?hidden=${this.step !== 'contact'}>\n <form part=\"form\" @submit=${this.onContact}>\n <h2 part=\"section-title\">Contact Information</h2>\n ${renderField({\n id: 'contact-email',\n name: 'email',\n label: 'Email Address',\n type: 'email',\n autocomplete: 'email',\n required: true,\n placeholder: 'you@example.com',\n })}\n <div part=\"actions\">\n <button\n type=\"submit\"\n part=\"submit\"\n class=\"btn btn-primary\"\n data-testid=\"contact-continue\"\n @click=${this.onSubmitClick}\n >\n <slot name=\"contact-continue-label\">Continue to Shipping</slot>\n </button>\n </div>\n </form>\n </section>\n `\n }\n\n private renderShipping() {\n return html`\n <section part=\"section\" data-testid=\"checkout-shipping\" ?hidden=${this.step !== 'shipping'}>\n <form part=\"form\" novalidate @submit=${this.onShipping}>\n <h2 part=\"section-title\">Shipping Address</h2>\n <nymbal-address-form id-prefix=\"shipping\" data-testid=\"shipping-form\"></nymbal-address-form>\n <div part=\"actions\">\n <button type=\"button\" part=\"back\" class=\"btn btn-secondary\" @click=${this.back('contact')}>\n Back\n </button>\n <button\n type=\"submit\"\n part=\"submit\"\n class=\"btn btn-primary\"\n data-testid=\"shipping-continue\"\n @click=${this.onSubmitClick}\n >\n <slot name=\"shipping-continue-label\">Continue to Payment</slot>\n </button>\n </div>\n </form>\n </section>\n `\n }\n\n private renderPayment() {\n const busy = this.placing || this.placed\n return html`\n <section part=\"section\" data-testid=\"checkout-payment\" ?hidden=${this.step !== 'payment'}>\n <form part=\"form\" novalidate @submit=${this.onPlace}>\n <h2 part=\"section-title\">Payment</h2>\n <label part=\"billing-same\">\n <input\n type=\"checkbox\"\n id=\"billing-same\"\n data-testid=\"billing-same\"\n .checked=${this.billingSame}\n @change=${this.onBillingSame}\n />\n Billing address same as shipping\n </label>\n <div part=\"billing\" data-testid=\"billing-address\" ?hidden=${this.billingSame}>\n <h3 part=\"billing-title\">Billing Address</h3>\n <nymbal-address-form id-prefix=\"billing\"></nymbal-address-form>\n </div>\n <div part=\"payment\"><slot name=\"payment\"></slot></div>\n <div part=\"actions\">\n <button\n type=\"button\"\n part=\"back\"\n class=\"btn btn-secondary\"\n ?disabled=${busy}\n @click=${this.back('shipping')}\n >\n Back\n </button>\n <button\n type=\"submit\"\n part=\"submit\"\n class=\"btn btn-primary\"\n data-testid=\"place-order\"\n ?disabled=${busy || this.paymentBlocked()}\n @click=${this.onSubmitClick}\n >\n ${this.placing\n ? 'Processing...'\n : html`<slot name=\"place-order-label\">Place Order</slot>`}\n </button>\n </div>\n <p part=\"terms\">\n <slot name=\"terms\">By placing your order, you agree to our terms and conditions.</slot>\n </p>\n </form>\n </section>\n `\n }\n\n private renderLine(item: CartItem, currency: string) {\n return html`\n <div part=\"item\">\n <span part=\"item-name\">${item.productName}</span>\n ${item.variantName ? html`<span part=\"item-variant\">${item.variantName}</span>` : nothing}\n <span part=\"item-qty\">x${item.qty}</span>\n <span part=\"item-price\">${formatPrice(item.priceMinor * item.qty, currency)}</span>\n </div>\n `\n }\n\n private renderSummary() {\n const cart = shownCart(this, this.cart, this.readsStores ? this.client : null)\n const currency = cart.currency || 'GBP'\n const subtotal = formatPrice(cart.subtotalMinor, currency)\n return html`\n <aside part=\"summary\" data-testid=\"checkout-summary\">\n <h2 part=\"summary-title\"><slot name=\"summary-title\">Order Summary</slot></h2>\n ${cart.items.length === 0\n ? nothing\n : html`<div part=\"items\">\n ${cart.items.map((item) => this.renderLine(item, currency))}\n </div>`}\n <dl>\n <div part=\"summary-row subtotal\">\n <dt>Subtotal</dt>\n <dd data-testid=\"cart-subtotal\">${subtotal}</dd>\n </div>\n <div part=\"summary-row\">\n <dt>Shipping</dt>\n <dd>Standard, free</dd>\n </div>\n </dl>\n <div part=\"total\">\n <span>Total</span>\n <span>${subtotal}</span>\n </div>\n </aside>\n `\n }\n\n protected override render() {\n return html`\n <h1 part=\"title\"><slot name=\"title\">Checkout</slot></h1>\n ${this.renderSteps()}\n ${this.error === null\n ? nothing\n : html`<div part=\"error\" role=\"alert\" data-testid=\"checkout-error\">${this.error}</div>`}\n <div part=\"layout\">\n <div part=\"main\">${this.renderContact()} ${this.renderShipping()} ${this.renderPayment()}</div>\n ${this.renderSummary()}\n </div>\n `\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,114 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalElement,
4
+ baseStyles,
5
+ parsedJson,
6
+ propertiesOf
7
+ } from "./chunk-EYDMJNNK.js";
8
+
9
+ // src/components/review-list.ts
10
+ import { css, html } from "lit";
11
+ var SCHEMA = {
12
+ tag: "nymbal-review-list",
13
+ reactName: "ReviewList",
14
+ // A product's approved reviews, as `GET /api/products/:id/reviews` serves them.
15
+ attributes: [{ name: "reviews", prop: "reviews", type: "json" }],
16
+ events: [],
17
+ parts: ["list", "review", "rating", "empty"],
18
+ slots: ["empty"],
19
+ children: false
20
+ };
21
+ var DATE = new Intl.DateTimeFormat("en-GB", {
22
+ day: "numeric",
23
+ month: "short",
24
+ year: "numeric",
25
+ timeZone: "UTC"
26
+ });
27
+ function dateOf(iso) {
28
+ const date = new Date(iso);
29
+ return Number.isNaN(date.getTime()) ? "" : DATE.format(date);
30
+ }
31
+ function stars(rating) {
32
+ const filled = Math.max(0, Math.min(5, Math.round(rating)));
33
+ return "\u2605".repeat(filled) + "\u2606".repeat(5 - filled);
34
+ }
35
+ var NymbalReviewList = class extends NymbalElement {
36
+ static schema = SCHEMA;
37
+ static properties = propertiesOf(SCHEMA);
38
+ static styles = [
39
+ baseStyles,
40
+ css`
41
+ :host {
42
+ display: block;
43
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
44
+ color: var(--nymbal-color-text, #18181b);
45
+ }
46
+ [part~='list'] {
47
+ display: flex;
48
+ flex-direction: column;
49
+ gap: var(--nymbal-spacing-lg, 1.5rem);
50
+ }
51
+ [part~='review'] {
52
+ padding-bottom: var(--nymbal-spacing-lg, 1.5rem);
53
+ border-bottom: 1px solid var(--nymbal-color-border, #e4e4e7);
54
+ }
55
+ .meta {
56
+ display: flex;
57
+ justify-content: space-between;
58
+ align-items: center;
59
+ margin-bottom: var(--nymbal-spacing-sm, 0.5rem);
60
+ }
61
+ [part~='rating'] {
62
+ letter-spacing: 0.1em;
63
+ color: var(--nymbal-color-warning, #d97706);
64
+ }
65
+ .date {
66
+ font-size: var(--nymbal-font-size-xs, 0.75rem);
67
+ color: var(--nymbal-color-text-muted, #71717a);
68
+ }
69
+ h3 {
70
+ margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);
71
+ line-height: 1.2;
72
+ font-size: var(--nymbal-font-size-base, 1rem);
73
+ font-weight: var(--nymbal-font-weight-medium, 500);
74
+ }
75
+ p {
76
+ margin: 0;
77
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
78
+ line-height: 1.6;
79
+ color: var(--nymbal-color-text-muted, #71717a);
80
+ }
81
+ `
82
+ ];
83
+ render() {
84
+ const reviews = parsedJson(this, "reviews", this.reviews);
85
+ if (!Array.isArray(reviews) || reviews.length === 0) {
86
+ return html`<p part="empty">
87
+ <slot name="empty">No reviews yet. Be the first to review this product.</slot>
88
+ </p>`;
89
+ }
90
+ return html`
91
+ <div part="list" data-testid="review-list">
92
+ ${reviews.map(
93
+ (r) => html`
94
+ <article part="review" data-testid="review-${r.id}">
95
+ <div class="meta">
96
+ <span part="rating" aria-label="${r.rating} out of 5 stars"
97
+ >${stars(r.rating)}</span
98
+ >
99
+ <span class="date">${dateOf(r.submittedAt)}</span>
100
+ </div>
101
+ <h3>${r.title}</h3>
102
+ <p>${r.body}</p>
103
+ </article>
104
+ `
105
+ )}
106
+ </div>
107
+ `;
108
+ }
109
+ };
110
+
111
+ export {
112
+ NymbalReviewList
113
+ };
114
+ //# sourceMappingURL=chunk-7JAAOVCE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/review-list.ts"],"sourcesContent":["import { css, html } from 'lit'\nimport {\n baseStyles,\n NymbalElement,\n parsedJson,\n propertiesOf,\n type ElementSchema,\n} from '../base-element.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-review-list',\n reactName: 'ReviewList',\n // A product's approved reviews, as `GET /api/products/:id/reviews` serves them.\n attributes: [{ name: 'reviews', prop: 'reviews', type: 'json' }],\n events: [],\n parts: ['list', 'review', 'rating', 'empty'],\n slots: ['empty'],\n children: false,\n}\n\ninterface Review {\n id: string\n rating: number\n title: string\n body: string\n submittedAt: string\n}\n\n// UTC, so the server's date and the browser's are one.\nconst DATE = new Intl.DateTimeFormat('en-GB', {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n timeZone: 'UTC',\n})\n\nfunction dateOf(iso: string): string {\n const date = new Date(iso)\n return Number.isNaN(date.getTime()) ? '' : DATE.format(date)\n}\n\nfunction stars(rating: number): string {\n const filled = Math.max(0, Math.min(5, Math.round(rating)))\n return '★'.repeat(filled) + '☆'.repeat(5 - filled)\n}\n\n/** A product's reviews, each with its rating, date, title and text; an invitation when there are none. */\nexport class NymbalReviewList extends NymbalElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n css`\n :host {\n display: block;\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n color: var(--nymbal-color-text, #18181b);\n }\n [part~='list'] {\n display: flex;\n flex-direction: column;\n gap: var(--nymbal-spacing-lg, 1.5rem);\n }\n [part~='review'] {\n padding-bottom: var(--nymbal-spacing-lg, 1.5rem);\n border-bottom: 1px solid var(--nymbal-color-border, #e4e4e7);\n }\n .meta {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: var(--nymbal-spacing-sm, 0.5rem);\n }\n [part~='rating'] {\n letter-spacing: 0.1em;\n color: var(--nymbal-color-warning, #d97706);\n }\n .date {\n font-size: var(--nymbal-font-size-xs, 0.75rem);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n h3 {\n margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);\n line-height: 1.2;\n font-size: var(--nymbal-font-size-base, 1rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n p {\n margin: 0;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n line-height: 1.6;\n color: var(--nymbal-color-text-muted, #71717a);\n }\n `,\n ]\n\n declare reviews: unknown\n\n protected override render() {\n const reviews = parsedJson<Review[]>(this, 'reviews', this.reviews)\n if (!Array.isArray(reviews) || reviews.length === 0) {\n return html`<p part=\"empty\">\n <slot name=\"empty\">No reviews yet. Be the first to review this product.</slot>\n </p>`\n }\n return html`\n <div part=\"list\" data-testid=\"review-list\">\n ${reviews.map(\n (r) => html`\n <article part=\"review\" data-testid=\"review-${r.id}\">\n <div class=\"meta\">\n <span part=\"rating\" aria-label=\"${r.rating} out of 5 stars\"\n >${stars(r.rating)}</span\n >\n <span class=\"date\">${dateOf(r.submittedAt)}</span>\n </div>\n <h3>${r.title}</h3>\n <p>${r.body}</p>\n </article>\n `,\n )}\n </div>\n `\n }\n}\n"],"mappings":";;;;;;;;;AAAA,SAAS,KAAK,YAAY;AAS1B,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAEX,YAAY,CAAC,EAAE,MAAM,WAAW,MAAM,WAAW,MAAM,OAAO,CAAC;AAAA,EAC/D,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,QAAQ,UAAU,UAAU,OAAO;AAAA,EAC3C,OAAO,CAAC,OAAO;AAAA,EACf,UAAU;AACZ;AAWA,IAAM,OAAO,IAAI,KAAK,eAAe,SAAS;AAAA,EAC5C,KAAK;AAAA,EACL,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU;AACZ,CAAC;AAED,SAAS,OAAO,KAAqB;AACnC,QAAM,OAAO,IAAI,KAAK,GAAG;AACzB,SAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,KAAK,KAAK,OAAO,IAAI;AAC7D;AAEA,SAAS,MAAM,QAAwB;AACrC,QAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,CAAC,CAAC;AAC1D,SAAO,SAAI,OAAO,MAAM,IAAI,SAAI,OAAO,IAAI,MAAM;AACnD;AAGO,IAAM,mBAAN,cAA+B,cAAc;AAAA,EAClD,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAChD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0CF;AAAA,EAImB,SAAS;AAC1B,UAAM,UAAU,WAAqB,MAAM,WAAW,KAAK,OAAO;AAClE,QAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW,GAAG;AACnD,aAAO;AAAA;AAAA;AAAA,IAGT;AACA,WAAO;AAAA;AAAA,UAED,QAAQ;AAAA,MACR,CAAC,MAAM;AAAA,yDACwC,EAAE,EAAE;AAAA;AAAA,kDAEX,EAAE,MAAM;AAAA,qBACrC,MAAM,EAAE,MAAM,CAAC;AAAA;AAAA,qCAEC,OAAO,EAAE,WAAW,CAAC;AAAA;AAAA,oBAEtC,EAAE,KAAK;AAAA,mBACR,EAAE,IAAI;AAAA;AAAA;AAAA,IAGjB,CAAC;AAAA;AAAA;AAAA,EAGP;AACF;","names":[]}
@@ -0,0 +1,206 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalAddressForm
4
+ } from "./chunk-V55K2CS5.js";
5
+ import {
6
+ NymbalFormElement
7
+ } from "./chunk-PN6QKYE4.js";
8
+ import {
9
+ buttonStyles,
10
+ fieldStyles
11
+ } from "./chunk-GHXTJ2CF.js";
12
+ import {
13
+ baseStyles,
14
+ parsedJson,
15
+ propertiesOf
16
+ } from "./chunk-EYDMJNNK.js";
17
+
18
+ // src/components/address-book.ts
19
+ import { css, html, nothing } from "lit";
20
+ var SCHEMA = {
21
+ tag: "nymbal-address-book",
22
+ reactName: "AddressBook",
23
+ // The addresses a server read, listed until the element's own list call answers.
24
+ attributes: [{ name: "addresses", prop: "addresses", type: "json" }],
25
+ events: [
26
+ {
27
+ name: "nymbal:address-book:changed",
28
+ prop: "onChanged",
29
+ detail: "{ address: unknown }"
30
+ }
31
+ ],
32
+ parts: [
33
+ "list",
34
+ "status",
35
+ "empty",
36
+ "empty-title",
37
+ "empty-text",
38
+ "address",
39
+ "address-badge",
40
+ "address-line",
41
+ "add",
42
+ "add-title",
43
+ "form",
44
+ "actions",
45
+ "submit",
46
+ "error",
47
+ "message"
48
+ ],
49
+ slots: ["add-title"],
50
+ children: false
51
+ };
52
+ var NymbalAddressBook = class extends NymbalFormElement {
53
+ static schema = SCHEMA;
54
+ static uses = [NymbalAddressForm];
55
+ static properties = propertiesOf(SCHEMA);
56
+ static styles = [
57
+ baseStyles,
58
+ buttonStyles,
59
+ fieldStyles,
60
+ css`
61
+ [part~='list'] {
62
+ display: grid;
63
+ grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
64
+ gap: var(--nymbal-spacing-md, 1rem);
65
+ }
66
+ [part~='status'],
67
+ [part~='empty-text'] {
68
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
69
+ color: var(--nymbal-color-text-muted, #71717a);
70
+ }
71
+ [part~='empty'] {
72
+ grid-column: 1 / -1;
73
+ }
74
+ [part~='empty-title'] {
75
+ margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);
76
+ font-size: var(--nymbal-font-size-base, 1rem);
77
+ font-weight: var(--nymbal-font-weight-medium, 500);
78
+ }
79
+ [part~='address'] {
80
+ position: relative;
81
+ padding: var(--nymbal-spacing-md, 1rem);
82
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
83
+ line-height: 1.6;
84
+ border: 1px solid var(--nymbal-color-border, #e4e4e7);
85
+ border-radius: var(--nymbal-radius-md, 0.5rem);
86
+ }
87
+ [part~='address-badge'] {
88
+ display: inline-block;
89
+ margin-bottom: var(--nymbal-spacing-xs, 0.25rem);
90
+ padding: 0 var(--nymbal-spacing-sm, 0.5rem);
91
+ font-size: var(--nymbal-font-size-xs, 0.75rem);
92
+ font-weight: var(--nymbal-font-weight-medium, 500);
93
+ color: var(--nymbal-color-text-inverse, #ffffff);
94
+ background: var(--nymbal-color-primary, #18181b);
95
+ border-radius: var(--nymbal-radius-full, 9999px);
96
+ }
97
+ [part~='add'] {
98
+ margin-top: var(--nymbal-spacing-xl, 2rem);
99
+ }
100
+ [part~='add-title'] {
101
+ margin: 0 0 var(--nymbal-spacing-md, 1rem);
102
+ font-size: var(--nymbal-font-size-lg, 1.125rem);
103
+ font-weight: var(--nymbal-font-weight-bold, 700);
104
+ }
105
+ [part~='submit'] {
106
+ width: auto;
107
+ }
108
+ `
109
+ ];
110
+ /** null until the first list answers. */
111
+ listed = null;
112
+ loadFailed = false;
113
+ connectedCallback() {
114
+ super.connectedCallback();
115
+ if (this.hasClient) void this.load();
116
+ }
117
+ /** The list's answer settles the book: the addresses a server gave give way to it. */
118
+ async load() {
119
+ try {
120
+ this.listed = await this.client.customer.addresses.list();
121
+ this.loadFailed = false;
122
+ } catch {
123
+ this.loadFailed = true;
124
+ }
125
+ this.addresses = void 0;
126
+ this.requestUpdate();
127
+ }
128
+ get addressForm() {
129
+ return this.renderRoot.querySelector("nymbal-address-form");
130
+ }
131
+ async send() {
132
+ const form = this.addressForm;
133
+ if (form === null) throw new Error("The address fields have not rendered.");
134
+ const address = await this.client.customer.addresses.create(form.value);
135
+ form.reset();
136
+ this.notice = "Address saved successfully.";
137
+ this.emit("nymbal:address-book:changed", { address });
138
+ await this.load();
139
+ }
140
+ failureText() {
141
+ return "Failed to save address. Please try again.";
142
+ }
143
+ onAddSubmit = (event) => {
144
+ if (!this.pending && this.addressForm?.reportValidity() === false) {
145
+ event.preventDefault();
146
+ return;
147
+ }
148
+ this.onSubmit(event);
149
+ };
150
+ renderAddress(a) {
151
+ return html`
152
+ <div part="address" data-testid="address-${a.id}">
153
+ ${a.isDefaultShipping ? html`<span part="address-badge">Default Shipping</span>` : nothing}
154
+ ${a.isDefaultBilling ? html`<span part="address-badge">Default Billing</span>` : nothing}
155
+ <div part="address-line">${a.firstName} ${a.lastName}</div>
156
+ <div part="address-line">${a.addressLine1}</div>
157
+ ${a.addressLine2 ? html`<div part="address-line">${a.addressLine2}</div>` : nothing}
158
+ <div part="address-line">${a.city}, ${a.region} ${a.postalCode}</div>
159
+ <div part="address-line">${a.country}</div>
160
+ </div>
161
+ `;
162
+ }
163
+ renderList() {
164
+ if (this.loadFailed) return html`<p part="status">Unable to load addresses.</p>`;
165
+ const addresses = parsedJson(this, "addresses", this.addresses) ?? this.listed;
166
+ if (addresses === null) return html`<p part="status">Loading addresses...</p>`;
167
+ if (addresses.length === 0) {
168
+ return html`
169
+ <div part="empty">
170
+ <h2 part="empty-title">No saved addresses</h2>
171
+ <p part="empty-text">Add an address below to get started.</p>
172
+ </div>
173
+ `;
174
+ }
175
+ return addresses.map((a) => this.renderAddress(a));
176
+ }
177
+ render() {
178
+ return html`
179
+ <div part="list" data-testid="addresses-list">${this.renderList()}</div>
180
+ <section part="add">
181
+ <h2 part="add-title"><slot name="add-title">Add New Address</slot></h2>
182
+ <form part="form" data-testid="add-address-form" @submit=${this.onAddSubmit}>
183
+ <nymbal-address-form id-prefix="new-address"></nymbal-address-form>
184
+ <div part="actions">
185
+ <button
186
+ type="submit"
187
+ part="submit"
188
+ class="btn btn-primary"
189
+ data-testid="save-address"
190
+ ?disabled=${this.pending}
191
+ >
192
+ ${this.pending ? "Saving..." : "Save Address"}
193
+ </button>
194
+ </div>
195
+ </form>
196
+ ${this.renderError()}
197
+ ${this.notice === null ? nothing : html`<p part="message" role="status">${this.notice}</p>`}
198
+ </section>
199
+ `;
200
+ }
201
+ };
202
+
203
+ export {
204
+ NymbalAddressBook
205
+ };
206
+ //# sourceMappingURL=chunk-B73QVSA5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/address-book.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport type { CustomerAddress } from '@nymbalhq/types'\nimport { baseStyles, parsedJson, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { NymbalFormElement } from '../form-element.js'\nimport { buttonStyles, fieldStyles } from '../view-styles.js'\nimport { NymbalAddressForm } from './address-form.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-address-book',\n reactName: 'AddressBook',\n // The addresses a server read, listed until the element's own list call answers.\n attributes: [{ name: 'addresses', prop: 'addresses', type: 'json' }],\n events: [\n {\n name: 'nymbal:address-book:changed',\n prop: 'onChanged',\n detail: '{ address: unknown }',\n },\n ],\n parts: [\n 'list',\n 'status',\n 'empty',\n 'empty-title',\n 'empty-text',\n 'address',\n 'address-badge',\n 'address-line',\n 'add',\n 'add-title',\n 'form',\n 'actions',\n 'submit',\n 'error',\n 'message',\n ],\n slots: ['add-title'],\n children: false,\n}\n\n/** The signed-in customer's saved addresses, and the form that adds one. */\nexport class NymbalAddressBook extends NymbalFormElement {\n static override schema = SCHEMA\n static uses = [NymbalAddressForm]\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n buttonStyles,\n fieldStyles,\n css`\n [part~='list'] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));\n gap: var(--nymbal-spacing-md, 1rem);\n }\n [part~='status'],\n [part~='empty-text'] {\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='empty'] {\n grid-column: 1 / -1;\n }\n [part~='empty-title'] {\n margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-base, 1rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='address'] {\n position: relative;\n padding: var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n line-height: 1.6;\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-md, 0.5rem);\n }\n [part~='address-badge'] {\n display: inline-block;\n margin-bottom: var(--nymbal-spacing-xs, 0.25rem);\n padding: 0 var(--nymbal-spacing-sm, 0.5rem);\n font-size: var(--nymbal-font-size-xs, 0.75rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n color: var(--nymbal-color-text-inverse, #ffffff);\n background: var(--nymbal-color-primary, #18181b);\n border-radius: var(--nymbal-radius-full, 9999px);\n }\n [part~='add'] {\n margin-top: var(--nymbal-spacing-xl, 2rem);\n }\n [part~='add-title'] {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-lg, 1.125rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n }\n [part~='submit'] {\n width: auto;\n }\n `,\n ]\n\n declare addresses: unknown\n /** null until the first list answers. */\n private listed: CustomerAddress[] | null = null\n private loadFailed = false\n\n override connectedCallback(): void {\n super.connectedCallback()\n if (this.hasClient) void this.load()\n }\n\n /** The list's answer settles the book: the addresses a server gave give way to it. */\n private async load(): Promise<void> {\n try {\n this.listed = await this.client.customer.addresses.list()\n this.loadFailed = false\n } catch {\n this.loadFailed = true\n }\n this.addresses = undefined\n this.requestUpdate()\n }\n\n private get addressForm(): NymbalAddressForm | null {\n return this.renderRoot.querySelector<NymbalAddressForm>('nymbal-address-form')\n }\n\n protected override async send(): Promise<void> {\n const form = this.addressForm\n if (form === null) throw new Error('The address fields have not rendered.')\n const address = await this.client.customer.addresses.create(form.value)\n form.reset()\n this.notice = 'Address saved successfully.'\n this.emit('nymbal:address-book:changed', { address })\n await this.load()\n }\n\n protected override failureText(): string {\n return 'Failed to save address. Please try again.'\n }\n\n private readonly onAddSubmit = (event: Event): void => {\n // The fields are in the address form's shadow root, out of reach of this form's own validation.\n if (!this.pending && this.addressForm?.reportValidity() === false) {\n event.preventDefault()\n return\n }\n this.onSubmit(event)\n }\n\n private renderAddress(a: CustomerAddress) {\n return html`\n <div part=\"address\" data-testid=\"address-${a.id}\">\n ${a.isDefaultShipping ? html`<span part=\"address-badge\">Default Shipping</span>` : nothing}\n ${a.isDefaultBilling ? html`<span part=\"address-badge\">Default Billing</span>` : nothing}\n <div part=\"address-line\">${a.firstName} ${a.lastName}</div>\n <div part=\"address-line\">${a.addressLine1}</div>\n ${a.addressLine2 ? html`<div part=\"address-line\">${a.addressLine2}</div>` : nothing}\n <div part=\"address-line\">${a.city}, ${a.region} ${a.postalCode}</div>\n <div part=\"address-line\">${a.country}</div>\n </div>\n `\n }\n\n private renderList() {\n if (this.loadFailed) return html`<p part=\"status\">Unable to load addresses.</p>`\n const addresses =\n parsedJson<CustomerAddress[]>(this, 'addresses', this.addresses) ?? this.listed\n if (addresses === null) return html`<p part=\"status\">Loading addresses...</p>`\n if (addresses.length === 0) {\n return html`\n <div part=\"empty\">\n <h2 part=\"empty-title\">No saved addresses</h2>\n <p part=\"empty-text\">Add an address below to get started.</p>\n </div>\n `\n }\n return addresses.map((a) => this.renderAddress(a))\n }\n\n protected override render() {\n return html`\n <div part=\"list\" data-testid=\"addresses-list\">${this.renderList()}</div>\n <section part=\"add\">\n <h2 part=\"add-title\"><slot name=\"add-title\">Add New Address</slot></h2>\n <form part=\"form\" data-testid=\"add-address-form\" @submit=${this.onAddSubmit}>\n <nymbal-address-form id-prefix=\"new-address\"></nymbal-address-form>\n <div part=\"actions\">\n <button\n type=\"submit\"\n part=\"submit\"\n class=\"btn btn-primary\"\n data-testid=\"save-address\"\n ?disabled=${this.pending}\n >\n ${this.pending ? 'Saving...' : 'Save Address'}\n </button>\n </div>\n </form>\n ${this.renderError()}\n ${this.notice === null\n ? nothing\n : html`<p part=\"message\" role=\"status\">${this.notice}</p>`}\n </section>\n `\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,17 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ parsedJson
4
+ } from "./chunk-EYDMJNNK.js";
5
+
6
+ // src/cart-projection.ts
7
+ var UNKNOWN = { items: [], subtotalMinor: 0, currency: "GBP", itemCount: 0, loading: true };
8
+ function shownCart(host, given, client) {
9
+ const cart = parsedJson(host, "cart", given);
10
+ if (cart !== void 0) return { ...cart, loading: false };
11
+ return client === null ? UNKNOWN : client.cart.getState();
12
+ }
13
+
14
+ export {
15
+ shownCart
16
+ };
17
+ //# sourceMappingURL=chunk-DR5QB5VN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cart-projection.ts"],"sourcesContent":["import type { NymbalClient } from '@nymbalhq/sdk'\nimport type { CartItem } from '@nymbalhq/types'\nimport { parsedJson } from './base-element.js'\n\n/**\n * The cart a server hands an element as its `cart` attribute: the lines and their sums, never the `GET /api/cart`\n * body, which carries the cart token the API keeps in an HttpOnly cookie.\n */\nexport interface CartProjection {\n items: CartItem[]\n subtotalMinor: number\n currency: string\n itemCount: number\n}\n\n/** What a cart element shows; `loading` until the cart is known. */\nexport interface ShownCart extends CartProjection {\n loading: boolean\n}\n\n/** No cart given and no store to read: the element's loading shell, never a false empty cart. */\nconst UNKNOWN: ShownCart = { items: [], subtotalMinor: 0, currency: 'GBP', itemCount: 0, loading: true }\n\n/** The cart a server gave the element while it stands, else the cart store's when there is a client. */\nexport function shownCart(host: Element, given: unknown, client: NymbalClient | null): ShownCart {\n const cart = parsedJson<CartProjection>(host, 'cart', given)\n if (cart !== undefined) return { ...cart, loading: false }\n return client === null ? UNKNOWN : client.cart.getState()\n}\n"],"mappings":";;;;;;AAqBA,IAAM,UAAqB,EAAE,OAAO,CAAC,GAAG,eAAe,GAAG,UAAU,OAAO,WAAW,GAAG,SAAS,KAAK;AAGhG,SAAS,UAAU,MAAe,OAAgB,QAAwC;AAC/F,QAAM,OAAO,WAA2B,MAAM,QAAQ,KAAK;AAC3D,MAAI,SAAS,OAAW,QAAO,EAAE,GAAG,MAAM,SAAS,MAAM;AACzD,SAAO,WAAW,OAAO,UAAU,OAAO,KAAK,SAAS;AAC1D;","names":[]}
@@ -0,0 +1,70 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalVariantElement
4
+ } from "./chunk-UYSZOEOA.js";
5
+ import {
6
+ formatPrice
7
+ } from "./chunk-RVSKX2RX.js";
8
+ import {
9
+ baseStyles,
10
+ propertiesOf
11
+ } from "./chunk-EYDMJNNK.js";
12
+
13
+ // src/components/product-price.ts
14
+ import { css, html, nothing } from "lit";
15
+ var SCHEMA = {
16
+ tag: "nymbal-product-price",
17
+ reactName: "ProductPrice",
18
+ // The product's first variant's price as a server read it, and the product whose store updates it may follow.
19
+ attributes: [
20
+ { name: "product-id", prop: "productId", type: "string" },
21
+ { name: "price-minor", prop: "priceMinor", type: "number" },
22
+ { name: "currency", prop: "currency", type: "string" },
23
+ { name: "compare-at-minor", prop: "compareAtMinor", type: "number" }
24
+ ],
25
+ events: [],
26
+ parts: ["price", "compare-at"],
27
+ slots: [],
28
+ children: false
29
+ };
30
+ var NymbalProductPrice = class extends NymbalVariantElement {
31
+ static schema = SCHEMA;
32
+ static properties = propertiesOf(SCHEMA);
33
+ static styles = [
34
+ baseStyles,
35
+ css`
36
+ :host {
37
+ display: block;
38
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
39
+ }
40
+ [part~='price'] {
41
+ font-size: 1.5rem;
42
+ font-weight: var(--nymbal-font-weight-bold, 700);
43
+ color: var(--nymbal-color-primary, #18181b);
44
+ }
45
+ [part~='compare-at'] {
46
+ margin-right: var(--nymbal-spacing-sm, 0.5rem);
47
+ font-size: 1.5rem;
48
+ font-weight: var(--nymbal-font-weight-normal, 400);
49
+ color: var(--nymbal-color-text-muted, #71717a);
50
+ text-decoration: line-through;
51
+ }
52
+ `
53
+ ];
54
+ render() {
55
+ const store = this.selected();
56
+ const currency = store?.product.currency ?? this.currency ?? "GBP";
57
+ const price = store === null ? this.priceMinor : store.variant?.priceMinor ?? store.product.priceMinor;
58
+ const compareAt = store === null ? this.compareAtMinor : void 0;
59
+ if (price === void 0 || price === null || Number.isNaN(price)) return nothing;
60
+ return html`
61
+ ${compareAt !== void 0 && compareAt > price ? html`<s part="compare-at">${formatPrice(compareAt, currency)}</s>` : nothing}
62
+ <span part="price" data-testid="product-price">${formatPrice(price, currency)}</span>
63
+ `;
64
+ }
65
+ };
66
+
67
+ export {
68
+ NymbalProductPrice
69
+ };
70
+ //# sourceMappingURL=chunk-DXWMNB5F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/product-price.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport { baseStyles, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { formatPrice } from '../format.js'\nimport { NymbalVariantElement } from '../selected-variant.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-product-price',\n reactName: 'ProductPrice',\n // The product's first variant's price as a server read it, and the product whose store updates it may follow.\n attributes: [\n { name: 'product-id', prop: 'productId', type: 'string' },\n { name: 'price-minor', prop: 'priceMinor', type: 'number' },\n { name: 'currency', prop: 'currency', type: 'string' },\n { name: 'compare-at-minor', prop: 'compareAtMinor', type: 'number' },\n ],\n events: [],\n parts: ['price', 'compare-at'],\n slots: [],\n children: false,\n}\n\n/** A product page's price: the selected variant's once the product store holds this product. */\nexport class NymbalProductPrice extends NymbalVariantElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n css`\n :host {\n display: block;\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n }\n [part~='price'] {\n font-size: 1.5rem;\n font-weight: var(--nymbal-font-weight-bold, 700);\n color: var(--nymbal-color-primary, #18181b);\n }\n [part~='compare-at'] {\n margin-right: var(--nymbal-spacing-sm, 0.5rem);\n font-size: 1.5rem;\n font-weight: var(--nymbal-font-weight-normal, 400);\n color: var(--nymbal-color-text-muted, #71717a);\n text-decoration: line-through;\n }\n `,\n ]\n\n declare priceMinor: number | undefined\n declare currency: string | undefined\n declare compareAtMinor: number | undefined\n\n protected override render() {\n const store = this.selected()\n const currency = store?.product.currency ?? this.currency ?? 'GBP'\n const price =\n store === null ? this.priceMinor : (store.variant?.priceMinor ?? store.product.priceMinor)\n // A variant carries no compare-at price, so one the server was given stands for the first variant only.\n const compareAt = store === null ? this.compareAtMinor : undefined\n if (price === undefined || price === null || Number.isNaN(price)) return nothing\n return html`\n ${compareAt !== undefined && compareAt > price\n ? html`<s part=\"compare-at\">${formatPrice(compareAt, currency)}</s>`\n : nothing}\n <span part=\"price\" data-testid=\"product-price\">${formatPrice(price, currency)}</span>\n `\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,KAAK,MAAM,eAAe;AAKnC,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAEX,YAAY;AAAA,IACV,EAAE,MAAM,cAAc,MAAM,aAAa,MAAM,SAAS;AAAA,IACxD,EAAE,MAAM,eAAe,MAAM,cAAc,MAAM,SAAS;AAAA,IAC1D,EAAE,MAAM,YAAY,MAAM,YAAY,MAAM,SAAS;AAAA,IACrD,EAAE,MAAM,oBAAoB,MAAM,kBAAkB,MAAM,SAAS;AAAA,EACrE;AAAA,EACA,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,SAAS,YAAY;AAAA,EAC7B,OAAO,CAAC;AAAA,EACR,UAAU;AACZ;AAGO,IAAM,qBAAN,cAAiC,qBAAqB;AAAA,EAC3D,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAChD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBF;AAAA,EAMmB,SAAS;AAC1B,UAAM,QAAQ,KAAK,SAAS;AAC5B,UAAM,WAAW,OAAO,QAAQ,YAAY,KAAK,YAAY;AAC7D,UAAM,QACJ,UAAU,OAAO,KAAK,aAAc,MAAM,SAAS,cAAc,MAAM,QAAQ;AAEjF,UAAM,YAAY,UAAU,OAAO,KAAK,iBAAiB;AACzD,QAAI,UAAU,UAAa,UAAU,QAAQ,OAAO,MAAM,KAAK,EAAG,QAAO;AACzE,WAAO;AAAA,QACH,cAAc,UAAa,YAAY,QACrC,4BAA4B,YAAY,WAAW,QAAQ,CAAC,SAC5D,OAAO;AAAA,uDACsC,YAAY,OAAO,QAAQ,CAAC;AAAA;AAAA,EAEjF;AACF;","names":[]}