@nymbalhq/web-components 1.0.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 +37 -105
  176. package/dist/index.js +130 -1098
  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,154 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ formatPrice
4
+ } from "./chunk-RVSKX2RX.js";
5
+ import {
6
+ navigateOnClick
7
+ } from "./chunk-LO2EW5YP.js";
8
+ import {
9
+ NymbalElement,
10
+ baseStyles,
11
+ propertiesOf
12
+ } from "./chunk-EYDMJNNK.js";
13
+
14
+ // src/components/product-card.ts
15
+ import { css, html, nothing } from "lit";
16
+ var SCHEMA = {
17
+ tag: "nymbal-product-card",
18
+ reactName: "ProductCard",
19
+ // `eager`: the card is in a grid's first row, so its image is the page's likely LCP and is fetched at once.
20
+ attributes: [
21
+ { name: "product", prop: "product", type: "json" },
22
+ { name: "eager", prop: "eager", type: "boolean" }
23
+ ],
24
+ events: [],
25
+ parts: ["card", "media", "image", "body", "name", "price"],
26
+ slots: [],
27
+ children: false
28
+ };
29
+ function cardImageOf(p) {
30
+ if (typeof p.imageUrl === "string" && p.imageUrl !== "") return { url: p.imageUrl, alt: p.name };
31
+ const media = p.media?.find((m) => typeof m.url === "string" && m.url !== "");
32
+ if (media !== void 0) return { url: media.url, alt: media.alt ?? p.name };
33
+ return { url: `https://picsum.photos/seed/${encodeURIComponent(p.slug)}/600/600`, alt: p.name };
34
+ }
35
+ function cardProductOf(p) {
36
+ const list = Array.isArray(p.media) ? p.media : [];
37
+ const media = list.find((m) => typeof m.url === "string" && m.url !== "");
38
+ const image = typeof p.imageUrl === "string" && p.imageUrl !== "" ? { imageUrl: p.imageUrl } : media === void 0 ? {} : { media: [{ url: media.url, ...media.alt !== void 0 && { alt: media.alt } }] };
39
+ return {
40
+ slug: p.slug,
41
+ name: p.name,
42
+ priceMinor: p.priceMinor ?? null,
43
+ priceRange: p.priceRange ?? null,
44
+ currency: p.currency ?? null,
45
+ ...image
46
+ };
47
+ }
48
+ function parse(raw) {
49
+ if (raw === void 0 || raw === "") return null;
50
+ try {
51
+ const value = JSON.parse(raw);
52
+ return value !== null && typeof value.slug === "string" ? value : null;
53
+ } catch {
54
+ console.warn("[nymbal-product-card] the product attribute is not JSON", raw);
55
+ return null;
56
+ }
57
+ }
58
+ function priceOf(p) {
59
+ const from = p.priceRange?.minMinor ?? p.priceMinor;
60
+ return from == null ? null : formatPrice(from, p.currency ?? "GBP");
61
+ }
62
+ var NymbalProductCard = class extends NymbalElement {
63
+ static schema = SCHEMA;
64
+ static properties = propertiesOf(SCHEMA);
65
+ static styles = [
66
+ baseStyles,
67
+ css`
68
+ :host {
69
+ display: block;
70
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
71
+ }
72
+ [part~='card'] {
73
+ display: flex;
74
+ flex-direction: column;
75
+ height: 100%;
76
+ color: var(--nymbal-color-text, #18181b);
77
+ text-decoration: none;
78
+ border: 1px solid var(--nymbal-color-border, #e4e4e7);
79
+ border-radius: var(--nymbal-radius-lg, 0.75rem);
80
+ overflow: hidden;
81
+ background: var(--nymbal-color-surface, #ffffff);
82
+ transition: box-shadow var(--nymbal-transition-fast, 150ms ease);
83
+ }
84
+ [part~='card']:hover {
85
+ box-shadow: var(--nymbal-shadow-md, 0 4px 6px rgba(0, 0, 0, 0.07));
86
+ }
87
+ [part~='card']:focus-visible {
88
+ outline: 2px solid var(--nymbal-color-accent, #2563eb);
89
+ outline-offset: 2px;
90
+ }
91
+ [part~='media'] {
92
+ aspect-ratio: 1 / 1;
93
+ background: #f4f4f5;
94
+ }
95
+ [part~='image'] {
96
+ display: block;
97
+ width: 100%;
98
+ height: 100%;
99
+ object-fit: cover;
100
+ }
101
+ [part~='body'] {
102
+ display: flex;
103
+ flex-direction: column;
104
+ gap: var(--nymbal-spacing-xs, 0.25rem);
105
+ padding: var(--nymbal-spacing-md, 1rem);
106
+ }
107
+ [part~='name'] {
108
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
109
+ font-weight: var(--nymbal-font-weight-medium, 500);
110
+ }
111
+ [part~='price'] {
112
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
113
+ font-weight: var(--nymbal-font-weight-bold, 700);
114
+ }
115
+ `
116
+ ];
117
+ render() {
118
+ const p = parse(this.product);
119
+ if (p === null) return nothing;
120
+ const image = cardImageOf(p);
121
+ const price = priceOf(p);
122
+ return html`
123
+ <a
124
+ part="card"
125
+ href="/product/${encodeURIComponent(p.slug)}"
126
+ data-testid="product-card-${p.slug}"
127
+ @click=${navigateOnClick}
128
+ >
129
+ <div part="media">
130
+ <img
131
+ part="image"
132
+ src=${image.url}
133
+ alt=${image.alt}
134
+ loading=${this.eager === true ? "eager" : "lazy"}
135
+ fetchpriority=${this.eager === true ? "high" : nothing}
136
+ width="600"
137
+ height="600"
138
+ />
139
+ </div>
140
+ <div part="body">
141
+ <span part="name">${p.name}</span>
142
+ ${price === null ? nothing : html`<span part="price">${price}</span>`}
143
+ </div>
144
+ </a>
145
+ `;
146
+ }
147
+ };
148
+
149
+ export {
150
+ cardImageOf,
151
+ cardProductOf,
152
+ NymbalProductCard
153
+ };
154
+ //# sourceMappingURL=chunk-RZVFA3N5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/product-card.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport { baseStyles, NymbalElement, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { formatPrice } from '../format.js'\nimport { navigateOnClick } from '../navigate.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-product-card',\n reactName: 'ProductCard',\n // `eager`: the card is in a grid's first row, so its image is the page's likely LCP and is fetched at once.\n attributes: [\n { name: 'product', prop: 'product', type: 'json' },\n { name: 'eager', prop: 'eager', type: 'boolean' },\n ],\n events: [],\n parts: ['card', 'media', 'image', 'body', 'name', 'price'],\n slots: [],\n children: false,\n}\n\n/** The fields a card reads; a search result or a listing product carries them. */\nexport interface CardProduct {\n slug: string\n name: string\n priceMinor?: number | null\n priceRange?: { minMinor: number; maxMinor: number } | null\n currency?: string | null\n imageUrl?: string | null\n media?: Array<{ url?: string; alt?: string }>\n}\n\n/**\n * The product's own image, else a placeholder seeded by its slug so a card without media keeps its shape. Exported so\n * a page preloads exactly the image its first card requests.\n */\nexport function cardImageOf(p: CardProduct): { url: string; alt: string } {\n if (typeof p.imageUrl === 'string' && p.imageUrl !== '') return { url: p.imageUrl, alt: p.name }\n const media = p.media?.find((m) => typeof m.url === 'string' && m.url !== '')\n if (media !== undefined) return { url: media.url!, alt: media.alt ?? p.name }\n return { url: `https://picsum.photos/seed/${encodeURIComponent(p.slug)}/600/600`, alt: p.name }\n}\n\n/**\n * A product as a card reads it, and nothing else: a listing's products carry their variants, descriptions and\n * categories, which a page of cards would otherwise put in its HTML once per card.\n */\nexport function cardProductOf(p: Omit<CardProduct, 'media'> & { media?: unknown }): CardProduct {\n const list = Array.isArray(p.media) ? (p.media as NonNullable<CardProduct['media']>) : []\n const media = list.find((m) => typeof m.url === 'string' && m.url !== '')\n const image =\n typeof p.imageUrl === 'string' && p.imageUrl !== ''\n ? { imageUrl: p.imageUrl }\n : media === undefined\n ? {}\n : { media: [{ url: media.url!, ...(media.alt !== undefined && { alt: media.alt }) }] }\n return {\n slug: p.slug,\n name: p.name,\n priceMinor: p.priceMinor ?? null,\n priceRange: p.priceRange ?? null,\n currency: p.currency ?? null,\n ...image,\n }\n}\n\nfunction parse(raw: string | undefined): CardProduct | null {\n if (raw === undefined || raw === '') return null\n try {\n const value = JSON.parse(raw) as Partial<CardProduct> | null\n return value !== null && typeof value.slug === 'string' ? (value as CardProduct) : null\n } catch {\n console.warn('[nymbal-product-card] the product attribute is not JSON', raw)\n return null\n }\n}\n\n/** The \"from\" price, which the price sorts order by: the cheapest variant's when the product has a range. */\nfunction priceOf(p: CardProduct): string | null {\n const from = p.priceRange?.minMinor ?? p.priceMinor\n return from == null ? null : formatPrice(from, p.currency ?? 'GBP')\n}\n\n/** One product as a link to its page: image, name and price. */\nexport class NymbalProductCard 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 }\n [part~='card'] {\n display: flex;\n flex-direction: column;\n height: 100%;\n color: var(--nymbal-color-text, #18181b);\n text-decoration: none;\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-lg, 0.75rem);\n overflow: hidden;\n background: var(--nymbal-color-surface, #ffffff);\n transition: box-shadow var(--nymbal-transition-fast, 150ms ease);\n }\n [part~='card']:hover {\n box-shadow: var(--nymbal-shadow-md, 0 4px 6px rgba(0, 0, 0, 0.07));\n }\n [part~='card']:focus-visible {\n outline: 2px solid var(--nymbal-color-accent, #2563eb);\n outline-offset: 2px;\n }\n [part~='media'] {\n aspect-ratio: 1 / 1;\n background: #f4f4f5;\n }\n [part~='image'] {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n [part~='body'] {\n display: flex;\n flex-direction: column;\n gap: var(--nymbal-spacing-xs, 0.25rem);\n padding: var(--nymbal-spacing-md, 1rem);\n }\n [part~='name'] {\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='price'] {\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n }\n `,\n ]\n\n declare product: string | undefined\n declare eager: boolean | undefined\n\n protected override render() {\n const p = parse(this.product)\n if (p === null) return nothing\n const image = cardImageOf(p)\n const price = priceOf(p)\n return html`\n <a\n part=\"card\"\n href=\"/product/${encodeURIComponent(p.slug)}\"\n data-testid=\"product-card-${p.slug}\"\n @click=${navigateOnClick}\n >\n <div part=\"media\">\n <img\n part=\"image\"\n src=${image.url}\n alt=${image.alt}\n loading=${this.eager === true ? 'eager' : 'lazy'}\n fetchpriority=${this.eager === true ? 'high' : nothing}\n width=\"600\"\n height=\"600\"\n />\n </div>\n <div part=\"body\">\n <span part=\"name\">${p.name}</span>\n ${price === null ? nothing : html`<span part=\"price\">${price}</span>`}\n </div>\n </a>\n `\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,135 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ formatPrice
4
+ } from "./chunk-RVSKX2RX.js";
5
+ import {
6
+ NymbalElement,
7
+ baseStyles,
8
+ parsedJson,
9
+ propertiesOf
10
+ } from "./chunk-EYDMJNNK.js";
11
+
12
+ // src/components/order-summary.ts
13
+ import { css, html, nothing } from "lit";
14
+ var SCHEMA = {
15
+ tag: "nymbal-order-summary",
16
+ reactName: "OrderSummary",
17
+ // An order's lines and money as `GET /api/orders/:n` serves them; the element adds nothing up itself.
18
+ attributes: [
19
+ { name: "items", prop: "items", type: "json" },
20
+ { name: "subtotal-minor", prop: "subtotalMinor", type: "number" },
21
+ { name: "discount-minor", prop: "discountMinor", type: "number" },
22
+ { name: "shipping-minor", prop: "shippingMinor", type: "number" },
23
+ { name: "tax-minor", prop: "taxMinor", type: "number" },
24
+ { name: "total-minor", prop: "totalMinor", type: "number" },
25
+ { name: "currency", prop: "currency", type: "string" }
26
+ ],
27
+ events: [],
28
+ parts: ["summary", "line", "totals", "subtotal", "discount", "shipping", "tax", "total"],
29
+ slots: [],
30
+ children: false
31
+ };
32
+ var known = (n) => n !== void 0 && !Number.isNaN(n);
33
+ var NymbalOrderSummary = class extends NymbalElement {
34
+ static schema = SCHEMA;
35
+ static properties = propertiesOf(SCHEMA);
36
+ static styles = [
37
+ baseStyles,
38
+ css`
39
+ :host {
40
+ display: block;
41
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
42
+ color: var(--nymbal-color-text, #18181b);
43
+ }
44
+ [part~='summary'] {
45
+ padding: var(--nymbal-spacing-lg, 1.5rem);
46
+ background-color: var(--nymbal-color-surface, #ffffff);
47
+ border: 1px solid var(--nymbal-color-border, #e4e4e7);
48
+ border-radius: var(--nymbal-radius-lg, 0.75rem);
49
+ }
50
+ h2 {
51
+ margin: 0 0 var(--nymbal-spacing-md, 1rem);
52
+ line-height: 1.2;
53
+ font-size: var(--nymbal-font-size-lg, 1.125rem);
54
+ font-weight: var(--nymbal-font-weight-bold, 700);
55
+ }
56
+ ul {
57
+ margin: 0 0 var(--nymbal-spacing-md, 1rem);
58
+ padding: 0;
59
+ list-style: none;
60
+ }
61
+ [part~='line'],
62
+ [part~='subtotal'],
63
+ [part~='discount'],
64
+ [part~='shipping'],
65
+ [part~='tax'],
66
+ [part~='total'] {
67
+ display: flex;
68
+ justify-content: space-between;
69
+ gap: var(--nymbal-spacing-md, 1rem);
70
+ padding-block: var(--nymbal-spacing-xs, 0.25rem);
71
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
72
+ }
73
+ .qty {
74
+ color: var(--nymbal-color-text-muted, #71717a);
75
+ }
76
+ [part~='totals'] {
77
+ margin: 0;
78
+ padding-top: var(--nymbal-spacing-sm, 0.5rem);
79
+ border-top: 1px solid var(--nymbal-color-border, #e4e4e7);
80
+ }
81
+ dt,
82
+ dd {
83
+ margin: 0;
84
+ }
85
+ [part~='discount'] dd {
86
+ color: var(--nymbal-color-success, #16a34a);
87
+ }
88
+ [part~='total'] {
89
+ margin-top: var(--nymbal-spacing-sm, 0.5rem);
90
+ padding-top: var(--nymbal-spacing-sm, 0.5rem);
91
+ border-top: 1px solid var(--nymbal-color-border, #e4e4e7);
92
+ font-size: var(--nymbal-font-size-base, 1rem);
93
+ font-weight: var(--nymbal-font-weight-bold, 700);
94
+ }
95
+ `
96
+ ];
97
+ render() {
98
+ const lines = parsedJson(this, "items", this.items);
99
+ const money = (minor) => formatPrice(minor, this.currency ?? "GBP");
100
+ const row = (part, label, minor, shown = money) => known(minor) ? html`<div part=${part}>
101
+ <dt>${label}</dt>
102
+ <dd>${shown(minor)}</dd>
103
+ </div>` : nothing;
104
+ return html`
105
+ <section part="summary" data-testid="order-summary">
106
+ <h2>Order Summary</h2>
107
+ <ul>
108
+ ${(Array.isArray(lines) ? lines : []).map(
109
+ (line) => html`
110
+ <li part="line">
111
+ <span>${line.name} <span class="qty">&times; ${line.qty}</span></span>
112
+ <span>${money(line.lineTotalMinor)}</span>
113
+ </li>
114
+ `
115
+ )}
116
+ </ul>
117
+ <dl part="totals">
118
+ ${row("subtotal", "Subtotal", this.subtotalMinor)}
119
+ ${known(this.discountMinor) && this.discountMinor !== 0 ? row("discount", "Discount", this.discountMinor, (m) => `\u2212${money(m)}`) : nothing}
120
+ ${row("shipping", "Shipping", this.shippingMinor, (m) => m === 0 ? "Free" : money(m))}
121
+ ${row("tax", "Tax", this.taxMinor)}
122
+ </dl>
123
+ ${known(this.totalMinor) ? html`<div part="total">
124
+ <span>Total</span>
125
+ <span>${money(this.totalMinor)}</span>
126
+ </div>` : nothing}
127
+ </section>
128
+ `;
129
+ }
130
+ };
131
+
132
+ export {
133
+ NymbalOrderSummary
134
+ };
135
+ //# sourceMappingURL=chunk-SM7TASEW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/order-summary.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport {\n baseStyles,\n NymbalElement,\n parsedJson,\n propertiesOf,\n type ElementSchema,\n} from '../base-element.js'\nimport { formatPrice } from '../format.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-order-summary',\n reactName: 'OrderSummary',\n // An order's lines and money as `GET /api/orders/:n` serves them; the element adds nothing up itself.\n attributes: [\n { name: 'items', prop: 'items', type: 'json' },\n { name: 'subtotal-minor', prop: 'subtotalMinor', type: 'number' },\n { name: 'discount-minor', prop: 'discountMinor', type: 'number' },\n { name: 'shipping-minor', prop: 'shippingMinor', type: 'number' },\n { name: 'tax-minor', prop: 'taxMinor', type: 'number' },\n { name: 'total-minor', prop: 'totalMinor', type: 'number' },\n { name: 'currency', prop: 'currency', type: 'string' },\n ],\n events: [],\n parts: ['summary', 'line', 'totals', 'subtotal', 'discount', 'shipping', 'tax', 'total'],\n slots: [],\n children: false,\n}\n\ninterface Line {\n name: string\n qty: number\n unitPriceMinor: number\n lineTotalMinor: number\n}\n\nconst known = (n: number | undefined): n is number => n !== undefined && !Number.isNaN(n)\n\n/** A placed order's lines, subtotal, discount (when there is one), shipping, tax and total. */\nexport class NymbalOrderSummary 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~='summary'] {\n padding: var(--nymbal-spacing-lg, 1.5rem);\n background-color: var(--nymbal-color-surface, #ffffff);\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-lg, 0.75rem);\n }\n h2 {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n line-height: 1.2;\n font-size: var(--nymbal-font-size-lg, 1.125rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n }\n ul {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n padding: 0;\n list-style: none;\n }\n [part~='line'],\n [part~='subtotal'],\n [part~='discount'],\n [part~='shipping'],\n [part~='tax'],\n [part~='total'] {\n display: flex;\n justify-content: space-between;\n gap: var(--nymbal-spacing-md, 1rem);\n padding-block: var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n }\n .qty {\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='totals'] {\n margin: 0;\n padding-top: var(--nymbal-spacing-sm, 0.5rem);\n border-top: 1px solid var(--nymbal-color-border, #e4e4e7);\n }\n dt,\n dd {\n margin: 0;\n }\n [part~='discount'] dd {\n color: var(--nymbal-color-success, #16a34a);\n }\n [part~='total'] {\n margin-top: var(--nymbal-spacing-sm, 0.5rem);\n padding-top: var(--nymbal-spacing-sm, 0.5rem);\n border-top: 1px solid var(--nymbal-color-border, #e4e4e7);\n font-size: var(--nymbal-font-size-base, 1rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n }\n `,\n ]\n\n declare items: unknown\n declare subtotalMinor: number | undefined\n declare discountMinor: number | undefined\n declare shippingMinor: number | undefined\n declare taxMinor: number | undefined\n declare totalMinor: number | undefined\n declare currency: string | undefined\n\n protected override render() {\n const lines = parsedJson<Line[]>(this, 'items', this.items)\n const money = (minor: number): string => formatPrice(minor, this.currency ?? 'GBP')\n const row = (part: string, label: string, minor: number | undefined, shown = money) =>\n known(minor)\n ? html`<div part=${part}>\n <dt>${label}</dt>\n <dd>${shown(minor)}</dd>\n </div>`\n : nothing\n return html`\n <section part=\"summary\" data-testid=\"order-summary\">\n <h2>Order Summary</h2>\n <ul>\n ${(Array.isArray(lines) ? lines : []).map(\n (line) => html`\n <li part=\"line\">\n <span>${line.name} <span class=\"qty\">&times; ${line.qty}</span></span>\n <span>${money(line.lineTotalMinor)}</span>\n </li>\n `,\n )}\n </ul>\n <dl part=\"totals\">\n ${row('subtotal', 'Subtotal', this.subtotalMinor)}\n ${known(this.discountMinor) && this.discountMinor !== 0\n ? row('discount', 'Discount', this.discountMinor, (m) => `−${money(m)}`)\n : nothing}\n ${row('shipping', 'Shipping', this.shippingMinor, (m) => (m === 0 ? 'Free' : money(m)))}\n ${row('tax', 'Tax', this.taxMinor)}\n </dl>\n ${known(this.totalMinor)\n ? html`<div part=\"total\">\n <span>Total</span>\n <span>${money(this.totalMinor)}</span>\n </div>`\n : nothing}\n </section>\n `\n }\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,KAAK,MAAM,eAAe;AAUnC,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAEX,YAAY;AAAA,IACV,EAAE,MAAM,SAAS,MAAM,SAAS,MAAM,OAAO;AAAA,IAC7C,EAAE,MAAM,kBAAkB,MAAM,iBAAiB,MAAM,SAAS;AAAA,IAChE,EAAE,MAAM,kBAAkB,MAAM,iBAAiB,MAAM,SAAS;AAAA,IAChE,EAAE,MAAM,kBAAkB,MAAM,iBAAiB,MAAM,SAAS;AAAA,IAChE,EAAE,MAAM,aAAa,MAAM,YAAY,MAAM,SAAS;AAAA,IACtD,EAAE,MAAM,eAAe,MAAM,cAAc,MAAM,SAAS;AAAA,IAC1D,EAAE,MAAM,YAAY,MAAM,YAAY,MAAM,SAAS;AAAA,EACvD;AAAA,EACA,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,WAAW,QAAQ,UAAU,YAAY,YAAY,YAAY,OAAO,OAAO;AAAA,EACvF,OAAO,CAAC;AAAA,EACR,UAAU;AACZ;AASA,IAAM,QAAQ,CAAC,MAAuC,MAAM,UAAa,CAAC,OAAO,MAAM,CAAC;AAGjF,IAAM,qBAAN,cAAiC,cAAc;AAAA,EACpD,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0DF;AAAA,EAUmB,SAAS;AAC1B,UAAM,QAAQ,WAAmB,MAAM,SAAS,KAAK,KAAK;AAC1D,UAAM,QAAQ,CAAC,UAA0B,YAAY,OAAO,KAAK,YAAY,KAAK;AAClF,UAAM,MAAM,CAAC,MAAc,OAAe,OAA2B,QAAQ,UAC3E,MAAM,KAAK,IACP,iBAAiB,IAAI;AAAA,kBACb,KAAK;AAAA,kBACL,MAAM,KAAK,CAAC;AAAA,oBAEpB;AACN,WAAO;AAAA;AAAA;AAAA;AAAA,aAIE,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,GAAG;AAAA,MACpC,CAAC,SAAS;AAAA;AAAA,wBAEE,KAAK,IAAI,8BAA8B,KAAK,GAAG;AAAA,wBAC/C,MAAM,KAAK,cAAc,CAAC;AAAA;AAAA;AAAA,IAGxC,CAAC;AAAA;AAAA;AAAA,YAGC,IAAI,YAAY,YAAY,KAAK,aAAa,CAAC;AAAA,YAC/C,MAAM,KAAK,aAAa,KAAK,KAAK,kBAAkB,IAClD,IAAI,YAAY,YAAY,KAAK,eAAe,CAAC,MAAM,SAAI,MAAM,CAAC,CAAC,EAAE,IACrE,OAAO;AAAA,YACT,IAAI,YAAY,YAAY,KAAK,eAAe,CAAC,MAAO,MAAM,IAAI,SAAS,MAAM,CAAC,CAAE,CAAC;AAAA,YACrF,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC;AAAA;AAAA,UAElC,MAAM,KAAK,UAAU,IACnB;AAAA;AAAA,sBAEU,MAAM,KAAK,UAAU,CAAC;AAAA,sBAEhC,OAAO;AAAA;AAAA;AAAA,EAGjB;AACF;","names":[]}
@@ -0,0 +1,124 @@
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/image-gallery.ts
10
+ import { css, html, nothing } from "lit";
11
+ var SCHEMA = {
12
+ tag: "nymbal-image-gallery",
13
+ reactName: "ImageGallery",
14
+ attributes: [{ name: "images", prop: "images", type: "json" }],
15
+ events: [],
16
+ parts: ["main", "thumbnails", "thumbnail"],
17
+ slots: [],
18
+ children: false
19
+ };
20
+ var NymbalImageGallery = class extends NymbalElement {
21
+ static schema = SCHEMA;
22
+ static properties = propertiesOf(SCHEMA);
23
+ static styles = [
24
+ baseStyles,
25
+ css`
26
+ :host {
27
+ display: block;
28
+ }
29
+ [part~='main'] {
30
+ display: block;
31
+ width: 100%;
32
+ height: auto;
33
+ aspect-ratio: 1 / 1;
34
+ object-fit: cover;
35
+ margin-bottom: var(--nymbal-spacing-sm, 0.5rem);
36
+ border-radius: var(--nymbal-radius-lg, 0.75rem);
37
+ background-color: var(--nymbal-color-border, #f4f4f5);
38
+ }
39
+ [part~='thumbnails'] {
40
+ display: flex;
41
+ gap: var(--nymbal-spacing-sm, 0.5rem);
42
+ overflow-x: auto;
43
+ padding-bottom: var(--nymbal-spacing-xs, 0.25rem);
44
+ }
45
+ [part~='thumbnail'] {
46
+ flex-shrink: 0;
47
+ width: 64px;
48
+ height: 64px;
49
+ padding: 0;
50
+ border: 2px solid transparent;
51
+ border-radius: var(--nymbal-radius-md, 0.5rem);
52
+ background: none;
53
+ overflow: hidden;
54
+ cursor: pointer;
55
+ transition: border-color var(--nymbal-transition-fast, 150ms ease);
56
+ }
57
+ [part~='thumbnail']:hover {
58
+ border-color: var(--nymbal-color-border, #e4e4e7);
59
+ }
60
+ [part~='thumbnail'][aria-pressed='true'] {
61
+ border-color: var(--nymbal-color-primary, #18181b);
62
+ }
63
+ [part~='thumbnail'] img {
64
+ display: block;
65
+ width: 100%;
66
+ height: 100%;
67
+ object-fit: cover;
68
+ }
69
+ @media (min-width: 641px) {
70
+ [part~='thumbnail'] {
71
+ width: 80px;
72
+ height: 80px;
73
+ }
74
+ }
75
+ `
76
+ ];
77
+ /** The image shown; a new list of images starts again at its first. */
78
+ shownIndex = 0;
79
+ willUpdate(changed) {
80
+ if (changed.has("images")) this.shownIndex = 0;
81
+ }
82
+ show(index) {
83
+ this.shownIndex = index;
84
+ this.requestUpdate();
85
+ }
86
+ render() {
87
+ const images = parsedJson(this, "images", this.images);
88
+ if (!Array.isArray(images) || images.length === 0) return nothing;
89
+ const main = images[this.shownIndex] ?? images[0];
90
+ return html`
91
+ <img
92
+ part="main"
93
+ src=${main.url}
94
+ alt=${main.alt ?? ""}
95
+ width="600"
96
+ height="600"
97
+ fetchpriority="high"
98
+ data-testid="product-gallery"
99
+ />
100
+ ${images.length > 1 ? html`
101
+ <div part="thumbnails">
102
+ ${images.map(
103
+ (image, i) => html`
104
+ <button
105
+ type="button"
106
+ part="thumbnail"
107
+ aria-label="Show image ${i + 1}"
108
+ aria-pressed=${i === this.shownIndex ? "true" : "false"}
109
+ @click=${() => this.show(i)}
110
+ >
111
+ <img src=${image.url} alt="" width="80" height="80" loading="lazy" />
112
+ </button>
113
+ `
114
+ )}
115
+ </div>
116
+ ` : nothing}
117
+ `;
118
+ }
119
+ };
120
+
121
+ export {
122
+ NymbalImageGallery
123
+ };
124
+ //# sourceMappingURL=chunk-U2PU4CWB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/image-gallery.ts"],"sourcesContent":["import { css, html, nothing, type PropertyValues } 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-image-gallery',\n reactName: 'ImageGallery',\n attributes: [{ name: 'images', prop: 'images', type: 'json' }],\n events: [],\n parts: ['main', 'thumbnails', 'thumbnail'],\n slots: [],\n children: false,\n}\n\ninterface Image {\n url: string\n alt?: string\n}\n\n/** A product's images: the one shown, and a thumbnail per image (when there are several) that shows it. */\nexport class NymbalImageGallery 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 }\n [part~='main'] {\n display: block;\n width: 100%;\n height: auto;\n aspect-ratio: 1 / 1;\n object-fit: cover;\n margin-bottom: var(--nymbal-spacing-sm, 0.5rem);\n border-radius: var(--nymbal-radius-lg, 0.75rem);\n background-color: var(--nymbal-color-border, #f4f4f5);\n }\n [part~='thumbnails'] {\n display: flex;\n gap: var(--nymbal-spacing-sm, 0.5rem);\n overflow-x: auto;\n padding-bottom: var(--nymbal-spacing-xs, 0.25rem);\n }\n [part~='thumbnail'] {\n flex-shrink: 0;\n width: 64px;\n height: 64px;\n padding: 0;\n border: 2px solid transparent;\n border-radius: var(--nymbal-radius-md, 0.5rem);\n background: none;\n overflow: hidden;\n cursor: pointer;\n transition: border-color var(--nymbal-transition-fast, 150ms ease);\n }\n [part~='thumbnail']:hover {\n border-color: var(--nymbal-color-border, #e4e4e7);\n }\n [part~='thumbnail'][aria-pressed='true'] {\n border-color: var(--nymbal-color-primary, #18181b);\n }\n [part~='thumbnail'] img {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n @media (min-width: 641px) {\n [part~='thumbnail'] {\n width: 80px;\n height: 80px;\n }\n }\n `,\n ]\n\n declare images: unknown\n\n /** The image shown; a new list of images starts again at its first. */\n private shownIndex = 0\n\n protected override willUpdate(changed: PropertyValues): void {\n if (changed.has('images')) this.shownIndex = 0\n }\n\n private show(index: number): void {\n this.shownIndex = index\n this.requestUpdate()\n }\n\n protected override render() {\n const images = parsedJson<Image[]>(this, 'images', this.images)\n if (!Array.isArray(images) || images.length === 0) return nothing\n const main = images[this.shownIndex] ?? images[0]!\n return html`\n <img\n part=\"main\"\n src=${main.url}\n alt=${main.alt ?? ''}\n width=\"600\"\n height=\"600\"\n fetchpriority=\"high\"\n data-testid=\"product-gallery\"\n />\n ${images.length > 1\n ? html`\n <div part=\"thumbnails\">\n ${images.map(\n (image, i) => html`\n <button\n type=\"button\"\n part=\"thumbnail\"\n aria-label=\"Show image ${i + 1}\"\n aria-pressed=${i === this.shownIndex ? 'true' : 'false'}\n @click=${() => this.show(i)}\n >\n <img src=${image.url} alt=\"\" width=\"80\" height=\"80\" loading=\"lazy\" />\n </button>\n `,\n )}\n </div>\n `\n : nothing}\n `\n }\n}\n"],"mappings":";;;;;;;;;AAAA,SAAS,KAAK,MAAM,eAAoC;AASxD,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA,EACX,YAAY,CAAC,EAAE,MAAM,UAAU,MAAM,UAAU,MAAM,OAAO,CAAC;AAAA,EAC7D,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,QAAQ,cAAc,WAAW;AAAA,EACzC,OAAO,CAAC;AAAA,EACR,UAAU;AACZ;AAQO,IAAM,qBAAN,cAAiC,cAAc;AAAA,EACpD,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmDF;AAAA;AAAA,EAKQ,aAAa;AAAA,EAEF,WAAW,SAA+B;AAC3D,QAAI,QAAQ,IAAI,QAAQ,EAAG,MAAK,aAAa;AAAA,EAC/C;AAAA,EAEQ,KAAK,OAAqB;AAChC,SAAK,aAAa;AAClB,SAAK,cAAc;AAAA,EACrB;AAAA,EAEmB,SAAS;AAC1B,UAAM,SAAS,WAAoB,MAAM,UAAU,KAAK,MAAM;AAC9D,QAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW,EAAG,QAAO;AAC1D,UAAM,OAAO,OAAO,KAAK,UAAU,KAAK,OAAO,CAAC;AAChD,WAAO;AAAA;AAAA;AAAA,cAGG,KAAK,GAAG;AAAA,cACR,KAAK,OAAO,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMpB,OAAO,SAAS,IACd;AAAA;AAAA,gBAEM,OAAO;AAAA,MACP,CAAC,OAAO,MAAM;AAAA;AAAA;AAAA;AAAA,6CAIe,IAAI,CAAC;AAAA,mCACf,MAAM,KAAK,aAAa,SAAS,OAAO;AAAA,6BAC9C,MAAM,KAAK,KAAK,CAAC,CAAC;AAAA;AAAA,+BAEhB,MAAM,GAAG;AAAA;AAAA;AAAA,IAG1B,CAAC;AAAA;AAAA,cAGL,OAAO;AAAA;AAAA,EAEf;AACF;","names":[]}
@@ -0,0 +1,5 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+
3
+ // src/hydrate-support.ts
4
+ import "@lit-labs/ssr-client/lit-element-hydrate-support.js";
5
+ //# sourceMappingURL=chunk-UCEVCLQ7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hydrate-support.ts"],"sourcesContent":["// Lets an element take over the shadow root a server rendered instead of rendering a second one. `lit-element` reads\n// the hook this installs once, when it evaluates, so this module is the first import of every entry and of\n// base-element: ESM evaluates imports depth-first in source order, and every component imports `lit` first.\nimport '@lit-labs/ssr-client/lit-element-hydrate-support.js'\n"],"mappings":";;;AAGA,OAAO;","names":[]}
@@ -0,0 +1,32 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalElement
4
+ } from "./chunk-EYDMJNNK.js";
5
+
6
+ // src/selected-variant.ts
7
+ var NymbalVariantElement = class extends NymbalElement {
8
+ following = false;
9
+ stores() {
10
+ return [this.client.product];
11
+ }
12
+ storeSettled() {
13
+ if (this.ownProduct() !== null) this.following = true;
14
+ }
15
+ ownProduct() {
16
+ const { product } = this.client.product.getState();
17
+ return product !== null && this.productId !== void 0 && product.id === this.productId ? product : null;
18
+ }
19
+ /** The store's product and its selected variant, once this element follows the store; else null. */
20
+ selected() {
21
+ if (!this.following || !this.readsStores) return null;
22
+ const product = this.ownProduct();
23
+ if (product === null) return null;
24
+ const { selectedVariant } = this.client.product.getState();
25
+ return { product, variant: selectedVariant ?? product.variants?.[0] ?? null };
26
+ }
27
+ };
28
+
29
+ export {
30
+ NymbalVariantElement
31
+ };
32
+ //# sourceMappingURL=chunk-UYSZOEOA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/selected-variant.ts"],"sourcesContent":["import type { DenormalisedProduct, DenormalisedVariant } from '@nymbalhq/sdk'\nimport { NymbalElement } from './base-element.js'\n\n/**\n * An element showing one product's selected variant (its price, its stock). It renders from its attributes until the\n * product store settles on the product its `product-id` names, then from the store's selected variant. A store\n * settled on another product (the page a soft navigation left) is not this element's, so it keeps its attributes.\n */\nexport abstract class NymbalVariantElement extends NymbalElement {\n declare productId: string | undefined\n\n private following = false\n\n protected override stores() {\n return [this.client.product]\n }\n\n protected override storeSettled(): void {\n if (this.ownProduct() !== null) this.following = true\n }\n\n private ownProduct(): DenormalisedProduct | null {\n const { product } = this.client.product.getState()\n return product !== null && this.productId !== undefined && product.id === this.productId\n ? product\n : null\n }\n\n /** The store's product and its selected variant, once this element follows the store; else null. */\n protected selected(): {\n product: DenormalisedProduct\n variant: DenormalisedVariant | null\n } | null {\n if (!this.following || !this.readsStores) return null\n const product = this.ownProduct()\n if (product === null) return null\n const { selectedVariant } = this.client.product.getState()\n return { product, variant: selectedVariant ?? product.variants?.[0] ?? null }\n }\n}\n"],"mappings":";;;;;;AAQO,IAAe,uBAAf,cAA4C,cAAc;AAAA,EAGvD,YAAY;AAAA,EAED,SAAS;AAC1B,WAAO,CAAC,KAAK,OAAO,OAAO;AAAA,EAC7B;AAAA,EAEmB,eAAqB;AACtC,QAAI,KAAK,WAAW,MAAM,KAAM,MAAK,YAAY;AAAA,EACnD;AAAA,EAEQ,aAAyC;AAC/C,UAAM,EAAE,QAAQ,IAAI,KAAK,OAAO,QAAQ,SAAS;AACjD,WAAO,YAAY,QAAQ,KAAK,cAAc,UAAa,QAAQ,OAAO,KAAK,YAC3E,UACA;AAAA,EACN;AAAA;AAAA,EAGU,WAGD;AACP,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,YAAa,QAAO;AACjD,UAAM,UAAU,KAAK,WAAW;AAChC,QAAI,YAAY,KAAM,QAAO;AAC7B,UAAM,EAAE,gBAAgB,IAAI,KAAK,OAAO,QAAQ,SAAS;AACzD,WAAO,EAAE,SAAS,SAAS,mBAAmB,QAAQ,WAAW,CAAC,KAAK,KAAK;AAAA,EAC9E;AACF;","names":[]}
@@ -0,0 +1,126 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ renderField
4
+ } from "./chunk-PN6QKYE4.js";
5
+ import {
6
+ fieldStyles
7
+ } from "./chunk-GHXTJ2CF.js";
8
+ import {
9
+ NymbalElement,
10
+ baseStyles,
11
+ propertiesOf
12
+ } from "./chunk-EYDMJNNK.js";
13
+
14
+ // src/components/address-form.ts
15
+ import { css, html } from "lit";
16
+ var SCHEMA = {
17
+ tag: "nymbal-address-form",
18
+ reactName: "AddressForm",
19
+ attributes: [{ name: "id-prefix", prop: "idPrefix", type: "string" }],
20
+ events: [],
21
+ parts: ["fields", "field", "label", "input"],
22
+ slots: [],
23
+ children: false
24
+ };
25
+ var FIELDS = [
26
+ { name: "firstName", label: "First Name", autocomplete: "given-name", required: true },
27
+ { name: "lastName", label: "Last Name", autocomplete: "family-name", required: true },
28
+ {
29
+ name: "addressLine1",
30
+ label: "Address Line 1",
31
+ autocomplete: "address-line1",
32
+ required: true,
33
+ full: true
34
+ },
35
+ {
36
+ name: "addressLine2",
37
+ label: "Address Line 2 (optional)",
38
+ autocomplete: "address-line2",
39
+ full: true
40
+ },
41
+ { name: "city", label: "City", autocomplete: "address-level2", required: true },
42
+ { name: "region", label: "State / County", autocomplete: "address-level1", required: true },
43
+ { name: "postalCode", label: "Postcode", autocomplete: "postal-code", required: true },
44
+ {
45
+ name: "country",
46
+ label: "Country (2-letter code)",
47
+ autocomplete: "country",
48
+ required: true,
49
+ placeholder: "GB"
50
+ }
51
+ ];
52
+ var NymbalAddressForm = class extends NymbalElement {
53
+ static schema = SCHEMA;
54
+ static properties = propertiesOf(SCHEMA);
55
+ static styles = [
56
+ baseStyles,
57
+ fieldStyles,
58
+ css`
59
+ [part~='fields'] {
60
+ display: grid;
61
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
62
+ gap: 0 var(--nymbal-spacing-md, 1rem);
63
+ }
64
+ .full {
65
+ grid-column: 1 / -1;
66
+ }
67
+ `
68
+ ];
69
+ input(name) {
70
+ return this.renderRoot.querySelector(`[name="${name}"]`);
71
+ }
72
+ /** The address as typed, trimmed; an empty second line is left out. */
73
+ get value() {
74
+ const field = (name) => this.input(name)?.value.trim() ?? "";
75
+ const line2 = field("addressLine2");
76
+ return {
77
+ firstName: field("firstName"),
78
+ lastName: field("lastName"),
79
+ addressLine1: field("addressLine1"),
80
+ ...line2 !== "" && { addressLine2: line2 },
81
+ city: field("city"),
82
+ region: field("region"),
83
+ postalCode: field("postalCode"),
84
+ country: field("country")
85
+ };
86
+ }
87
+ /** Shows the browser's message on the first field that is not valid, as a native form would. */
88
+ reportValidity() {
89
+ for (const input of this.renderRoot.querySelectorAll("input")) {
90
+ if (!input.reportValidity()) return false;
91
+ }
92
+ return true;
93
+ }
94
+ reset() {
95
+ for (const input of this.renderRoot.querySelectorAll("input")) input.value = "";
96
+ }
97
+ /**
98
+ * The inputs sit in this shadow root, so they have no form owner and Enter in one submits nothing natively. Enter
99
+ * submits the form that holds this element instead, as it would if the fields were its own.
100
+ */
101
+ onKeydown = (event) => {
102
+ if (event.key !== "Enter" || event.isComposing || event.defaultPrevented) return;
103
+ if (!(event.target instanceof HTMLInputElement)) return;
104
+ const form = this.closest("form");
105
+ if (form === null) return;
106
+ event.preventDefault();
107
+ form.requestSubmit();
108
+ };
109
+ render() {
110
+ const prefix = this.idPrefix ?? "address";
111
+ return html`
112
+ <div part="fields" @keydown=${this.onKeydown}>
113
+ ${FIELDS.map(
114
+ ({ full, ...f }) => html`<div class=${full ? "full" : ""}>
115
+ ${renderField({ ...f, id: `${prefix}-${f.name}` })}
116
+ </div>`
117
+ )}
118
+ </div>
119
+ `;
120
+ }
121
+ };
122
+
123
+ export {
124
+ NymbalAddressForm
125
+ };
126
+ //# sourceMappingURL=chunk-V55K2CS5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/address-form.ts"],"sourcesContent":["import { css, html } from 'lit'\nimport type { Address } from '@nymbalhq/types'\nimport { baseStyles, NymbalElement, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { renderField, type FieldSpec } from '../form-element.js'\nimport { fieldStyles } from '../view-styles.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-address-form',\n reactName: 'AddressForm',\n attributes: [{ name: 'id-prefix', prop: 'idPrefix', type: 'string' }],\n events: [],\n parts: ['fields', 'field', 'label', 'input'],\n slots: [],\n children: false,\n}\n\ntype FieldName = Exclude<keyof Address, 'company' | 'phone'>\n\nconst FIELDS: Array<Omit<FieldSpec, 'id' | 'name'> & { name: FieldName; full?: boolean }> = [\n { name: 'firstName', label: 'First Name', autocomplete: 'given-name', required: true },\n { name: 'lastName', label: 'Last Name', autocomplete: 'family-name', required: true },\n {\n name: 'addressLine1',\n label: 'Address Line 1',\n autocomplete: 'address-line1',\n required: true,\n full: true,\n },\n {\n name: 'addressLine2',\n label: 'Address Line 2 (optional)',\n autocomplete: 'address-line2',\n full: true,\n },\n { name: 'city', label: 'City', autocomplete: 'address-level2', required: true },\n { name: 'region', label: 'State / County', autocomplete: 'address-level1', required: true },\n { name: 'postalCode', label: 'Postcode', autocomplete: 'postal-code', required: true },\n {\n name: 'country',\n label: 'Country (2-letter code)',\n autocomplete: 'country',\n required: true,\n placeholder: 'GB',\n },\n]\n\n/**\n * The address fields, with ids and test ids `<id-prefix>-<field>`. It renders no `<form>`: the view that holds it owns\n * the submit (Enter in a field submits it) and reads `value`.\n */\nexport class NymbalAddressForm extends NymbalElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n fieldStyles,\n css`\n [part~='fields'] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));\n gap: 0 var(--nymbal-spacing-md, 1rem);\n }\n .full {\n grid-column: 1 / -1;\n }\n `,\n ]\n\n declare idPrefix: string | undefined\n\n private input(name: FieldName): HTMLInputElement | null {\n return this.renderRoot.querySelector<HTMLInputElement>(`[name=\"${name}\"]`)\n }\n\n /** The address as typed, trimmed; an empty second line is left out. */\n get value(): Address {\n const field = (name: FieldName): string => this.input(name)?.value.trim() ?? ''\n const line2 = field('addressLine2')\n return {\n firstName: field('firstName'),\n lastName: field('lastName'),\n addressLine1: field('addressLine1'),\n ...(line2 !== '' && { addressLine2: line2 }),\n city: field('city'),\n region: field('region'),\n postalCode: field('postalCode'),\n country: field('country'),\n }\n }\n\n /** Shows the browser's message on the first field that is not valid, as a native form would. */\n reportValidity(): boolean {\n for (const input of this.renderRoot.querySelectorAll('input')) {\n if (!input.reportValidity()) return false\n }\n return true\n }\n\n reset(): void {\n for (const input of this.renderRoot.querySelectorAll('input')) input.value = ''\n }\n\n /**\n * The inputs sit in this shadow root, so they have no form owner and Enter in one submits nothing natively. Enter\n * submits the form that holds this element instead, as it would if the fields were its own.\n */\n private readonly onKeydown = (event: KeyboardEvent): void => {\n if (event.key !== 'Enter' || event.isComposing || event.defaultPrevented) return\n if (!(event.target instanceof HTMLInputElement)) return\n const form = this.closest('form')\n if (form === null) return\n event.preventDefault()\n form.requestSubmit()\n }\n\n protected override render() {\n const prefix = this.idPrefix ?? 'address'\n return html`\n <div part=\"fields\" @keydown=${this.onKeydown}>\n ${FIELDS.map(\n ({ full, ...f }) =>\n html`<div class=${full ? 'full' : ''}>\n ${renderField({ ...f, id: `${prefix}-${f.name}` })}\n </div>`,\n )}\n </div>\n `\n }\n}\n"],"mappings":"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","names":[]}