@nymbalhq/web-components 1.1.0 → 1.3.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-5G3C53A2.js +666 -0
  12. package/dist/chunk-5G3C53A2.js.map +1 -0
  13. package/dist/chunk-5VWZ2U5K.js +149 -0
  14. package/dist/chunk-5VWZ2U5K.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-XPFGMYBV.js +137 -0
  80. package/dist/chunk-XPFGMYBV.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,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":[]}
@@ -0,0 +1,346 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalQuantitySelector
4
+ } from "./chunk-HPBC3OG7.js";
5
+ import {
6
+ formatPrice
7
+ } from "./chunk-RVSKX2RX.js";
8
+ import {
9
+ buttonStyles
10
+ } from "./chunk-GHXTJ2CF.js";
11
+ import {
12
+ shownCart
13
+ } from "./chunk-DR5QB5VN.js";
14
+ import {
15
+ NymbalElement,
16
+ baseStyles,
17
+ propertiesOf
18
+ } from "./chunk-EYDMJNNK.js";
19
+
20
+ // src/components/cart-page.ts
21
+ import { css, html, nothing } from "lit";
22
+ var SCHEMA = {
23
+ tag: "nymbal-cart-page",
24
+ reactName: "CartPage",
25
+ // The cart a server read (cart-projection.ts), shown until the cart store settles.
26
+ attributes: [{ name: "cart", prop: "cart", type: "json" }],
27
+ events: [],
28
+ parts: [
29
+ "loading",
30
+ "empty",
31
+ "empty-title",
32
+ "empty-text",
33
+ "continue",
34
+ "layout",
35
+ "header",
36
+ "title",
37
+ "count",
38
+ "items",
39
+ "item",
40
+ "item-image",
41
+ "item-info",
42
+ "item-name",
43
+ "item-variant",
44
+ "item-price",
45
+ "item-remove",
46
+ "item-qty",
47
+ "item-total",
48
+ "summary",
49
+ "summary-title",
50
+ "summary-row",
51
+ "subtotal",
52
+ "total",
53
+ "actions",
54
+ "checkout"
55
+ ],
56
+ slots: ["title", "empty", "checkout-label"],
57
+ children: false
58
+ };
59
+ var NymbalCartPage = class extends NymbalElement {
60
+ static schema = SCHEMA;
61
+ static uses = [NymbalQuantitySelector];
62
+ static properties = propertiesOf(SCHEMA);
63
+ static styles = [
64
+ baseStyles,
65
+ buttonStyles,
66
+ css`
67
+ :host {
68
+ display: block;
69
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
70
+ color: var(--nymbal-color-text, #18181b);
71
+ }
72
+ [part~='loading'],
73
+ [part~='empty'] {
74
+ padding: 4rem var(--nymbal-spacing-md, 1rem);
75
+ text-align: center;
76
+ }
77
+ /* Until the cart is known the page keeps what follows it below the fold, so a cart with lines does not push
78
+ the footer down as it arrives. */
79
+ [part~='loading'] {
80
+ min-height: 100vh;
81
+ }
82
+ [part~='empty-title'] {
83
+ margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);
84
+ font-size: var(--nymbal-font-size-lg, 1.125rem);
85
+ font-weight: var(--nymbal-font-weight-medium, 500);
86
+ }
87
+ [part~='empty-text'] {
88
+ margin: 0 0 var(--nymbal-spacing-lg, 1.5rem);
89
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
90
+ color: var(--nymbal-color-text-muted, #71717a);
91
+ }
92
+ [part~='layout'] {
93
+ display: grid;
94
+ grid-template-columns: 1fr;
95
+ gap: var(--nymbal-spacing-xl, 2rem);
96
+ }
97
+ @media (min-width: 769px) {
98
+ [part~='layout'] {
99
+ grid-template-columns: 1fr 360px;
100
+ align-items: start;
101
+ }
102
+ }
103
+ [part~='header'] {
104
+ margin-bottom: var(--nymbal-spacing-lg, 1.5rem);
105
+ }
106
+ [part~='title'] {
107
+ margin: 0;
108
+ font-size: 1.5rem;
109
+ font-weight: var(--nymbal-font-weight-bold, 700);
110
+ color: var(--nymbal-color-primary, #18181b);
111
+ }
112
+ [part~='count'] {
113
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
114
+ font-weight: var(--nymbal-font-weight-normal, 400);
115
+ color: var(--nymbal-color-text-muted, #71717a);
116
+ }
117
+ [part~='items'] {
118
+ display: flex;
119
+ flex-direction: column;
120
+ }
121
+ [part~='item'] {
122
+ display: grid;
123
+ grid-template-columns: 80px 1fr auto;
124
+ gap: var(--nymbal-spacing-md, 1rem);
125
+ padding: var(--nymbal-spacing-md, 1rem) 0;
126
+ border-bottom: 1px solid var(--nymbal-color-border, #e4e4e7);
127
+ align-items: start;
128
+ }
129
+ [part~='item']:first-child {
130
+ border-top: 1px solid var(--nymbal-color-border, #e4e4e7);
131
+ }
132
+ @media (min-width: 641px) {
133
+ [part~='item'] {
134
+ grid-template-columns: 100px 1fr auto auto;
135
+ gap: var(--nymbal-spacing-lg, 1.5rem);
136
+ align-items: center;
137
+ }
138
+ }
139
+ [part~='item-image'] {
140
+ display: block;
141
+ width: 100%;
142
+ aspect-ratio: 1 / 1;
143
+ object-fit: cover;
144
+ border-radius: var(--nymbal-radius-md, 0.5rem);
145
+ background-color: #f4f4f5;
146
+ }
147
+ [part~='item-info'] {
148
+ display: flex;
149
+ flex-direction: column;
150
+ gap: var(--nymbal-spacing-xs, 0.25rem);
151
+ min-width: 0;
152
+ }
153
+ [part~='item-name'] {
154
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
155
+ font-weight: var(--nymbal-font-weight-medium, 500);
156
+ color: var(--nymbal-color-text, #18181b);
157
+ text-decoration: none;
158
+ }
159
+ [part~='item-name']:hover {
160
+ color: var(--nymbal-color-accent, #2563eb);
161
+ }
162
+ [part~='item-variant'],
163
+ [part~='item-price'] {
164
+ font-size: var(--nymbal-font-size-xs, 0.75rem);
165
+ color: var(--nymbal-color-text-muted, #71717a);
166
+ }
167
+ [part~='item-remove'] {
168
+ align-self: start;
169
+ padding: 0;
170
+ font: inherit;
171
+ font-size: var(--nymbal-font-size-xs, 0.75rem);
172
+ color: var(--nymbal-color-text-muted, #71717a);
173
+ background: none;
174
+ border: none;
175
+ text-decoration: underline;
176
+ cursor: pointer;
177
+ }
178
+ [part~='item-remove']:hover {
179
+ color: var(--nymbal-color-error, #dc2626);
180
+ }
181
+ [part~='item-total'] {
182
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
183
+ font-weight: var(--nymbal-font-weight-bold, 700);
184
+ text-align: right;
185
+ white-space: nowrap;
186
+ }
187
+ [part~='summary'] {
188
+ position: sticky;
189
+ top: 80px;
190
+ padding: var(--nymbal-spacing-lg, 1.5rem);
191
+ background-color: #fafafa;
192
+ border: 1px solid var(--nymbal-color-border, #e4e4e7);
193
+ border-radius: var(--nymbal-radius-lg, 0.75rem);
194
+ }
195
+ [part~='summary-title'] {
196
+ margin: 0 0 var(--nymbal-spacing-md, 1rem);
197
+ padding-bottom: var(--nymbal-spacing-md, 1rem);
198
+ font-size: var(--nymbal-font-size-lg, 1.125rem);
199
+ font-weight: var(--nymbal-font-weight-bold, 700);
200
+ border-bottom: 1px solid var(--nymbal-color-border, #e4e4e7);
201
+ }
202
+ dl {
203
+ margin: 0;
204
+ }
205
+ dd {
206
+ margin: 0;
207
+ font-weight: var(--nymbal-font-weight-medium, 500);
208
+ }
209
+ [part~='summary-row'] {
210
+ display: flex;
211
+ justify-content: space-between;
212
+ padding: var(--nymbal-spacing-xs, 0.25rem) 0;
213
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
214
+ }
215
+ [part~='summary-row'] dt {
216
+ color: var(--nymbal-color-text-muted, #71717a);
217
+ }
218
+ [part~='total'] {
219
+ display: flex;
220
+ justify-content: space-between;
221
+ margin-top: var(--nymbal-spacing-md, 1rem);
222
+ padding-top: var(--nymbal-spacing-md, 1rem);
223
+ font-size: var(--nymbal-font-size-lg, 1.125rem);
224
+ font-weight: var(--nymbal-font-weight-bold, 700);
225
+ border-top: 1px solid var(--nymbal-color-border, #e4e4e7);
226
+ }
227
+ [part~='actions'] {
228
+ display: flex;
229
+ flex-direction: column;
230
+ gap: var(--nymbal-spacing-sm, 0.5rem);
231
+ margin-top: var(--nymbal-spacing-lg, 1.5rem);
232
+ }
233
+ `
234
+ ];
235
+ stores() {
236
+ return [this.client.cart];
237
+ }
238
+ storeSettled() {
239
+ this.cart = void 0;
240
+ }
241
+ /** The first line's image is at the top of the page and is often its LCP, so only the lines below it load lazily. */
242
+ renderLine(item, currency, index) {
243
+ return html`
244
+ <div part="item" data-testid="cart-item-${item.variantId}">
245
+ ${item.imageUrl ? html`<img
246
+ part="item-image"
247
+ src=${item.imageUrl}
248
+ alt=${item.productName}
249
+ width="100"
250
+ height="100"
251
+ loading=${index === 0 ? "eager" : "lazy"}
252
+ />` : html`<div part="item-image" aria-hidden="true"></div>`}
253
+ <div part="item-info">
254
+ <a part="item-name" href="/product/${encodeURIComponent(item.productId)}"
255
+ >${item.productName}</a
256
+ >
257
+ ${item.variantName ? html`<span part="item-variant">${item.variantName}</span>` : nothing}
258
+ <span part="item-price">${formatPrice(item.priceMinor, currency)} each</span>
259
+ <button
260
+ type="button"
261
+ part="item-remove"
262
+ data-testid="cart-remove-${item.variantId}"
263
+ aria-label="Remove ${item.productName} from cart"
264
+ @click=${() => void this.client.cart.removeItem(item.variantId)}
265
+ >
266
+ Remove
267
+ </button>
268
+ </div>
269
+ <div part="item-qty">
270
+ <nymbal-quantity-selector
271
+ value=${item.qty}
272
+ min="1"
273
+ max="99"
274
+ @nymbal:quantity:changed=${(e) => void this.client.cart.updateQuantity(item.variantId, e.detail.value)}
275
+ ></nymbal-quantity-selector>
276
+ </div>
277
+ <span part="item-total">${formatPrice(item.priceMinor * item.qty, currency)}</span>
278
+ </div>
279
+ `;
280
+ }
281
+ render() {
282
+ const state = shownCart(this, this.cart, this.readsStores ? this.client : null);
283
+ const { items } = state;
284
+ const currency = state.currency || "GBP";
285
+ if (state.loading && items.length === 0) {
286
+ return html`<div part="loading" role="status">Loading your cart...</div>`;
287
+ }
288
+ if (items.length === 0) {
289
+ return html`
290
+ <div part="empty">
291
+ <slot name="empty">
292
+ <h1 part="empty-title">Your cart is empty</h1>
293
+ <p part="empty-text">Looks like you haven't added anything yet.</p>
294
+ </slot>
295
+ <a part="continue" class="btn btn-primary" href="/products" data-testid="continue-shopping"
296
+ >Continue Shopping</a
297
+ >
298
+ </div>
299
+ `;
300
+ }
301
+ const subtotal = formatPrice(state.subtotalMinor, currency);
302
+ return html`
303
+ <div part="layout">
304
+ <div>
305
+ <div part="header">
306
+ <h1 part="title">
307
+ <slot name="title">Your Cart</slot>
308
+ <span part="count">(${items.length} ${items.length === 1 ? "item" : "items"})</span>
309
+ </h1>
310
+ </div>
311
+ <div part="items" data-testid="cart-items">
312
+ ${items.map((item, index) => this.renderLine(item, currency, index))}
313
+ </div>
314
+ </div>
315
+ <div part="summary" data-testid="cart-summary">
316
+ <h2 part="summary-title">Order Summary</h2>
317
+ <dl>
318
+ <div part="summary-row subtotal">
319
+ <dt>Subtotal</dt>
320
+ <dd>${subtotal}</dd>
321
+ </div>
322
+ <div part="summary-row">
323
+ <dt>Shipping</dt>
324
+ <dd>Calculated at checkout</dd>
325
+ </div>
326
+ </dl>
327
+ <div part="total">
328
+ <span>Estimated Total</span>
329
+ <span>${subtotal}</span>
330
+ </div>
331
+ <div part="actions">
332
+ <a part="checkout" class="btn btn-primary" href="/checkout" data-testid="checkout-btn"
333
+ ><slot name="checkout-label">Proceed to Checkout</slot></a
334
+ >
335
+ <a part="continue" class="btn btn-secondary" href="/products">Continue Shopping</a>
336
+ </div>
337
+ </div>
338
+ </div>
339
+ `;
340
+ }
341
+ };
342
+
343
+ export {
344
+ NymbalCartPage
345
+ };
346
+ //# sourceMappingURL=chunk-VTQVGNVC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/cart-page.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport type { CartItem } from '@nymbalhq/types'\nimport { baseStyles, NymbalElement, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { formatPrice } from '../format.js'\nimport { shownCart } from '../cart-projection.js'\nimport { NymbalQuantitySelector } from './quantity-selector.js'\nimport { buttonStyles } from '../view-styles.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-cart-page',\n reactName: 'CartPage',\n // The cart a server read (cart-projection.ts), shown until the cart store settles.\n attributes: [{ name: 'cart', prop: 'cart', type: 'json' }],\n events: [],\n parts: [\n 'loading',\n 'empty',\n 'empty-title',\n 'empty-text',\n 'continue',\n 'layout',\n 'header',\n 'title',\n 'count',\n 'items',\n 'item',\n 'item-image',\n 'item-info',\n 'item-name',\n 'item-variant',\n 'item-price',\n 'item-remove',\n 'item-qty',\n 'item-total',\n 'summary',\n 'summary-title',\n 'summary-row',\n 'subtotal',\n 'total',\n 'actions',\n 'checkout',\n ],\n slots: ['title', 'empty', 'checkout-label'],\n children: false,\n}\n\n/** The cart as a page: its lines, quantities and removal, and a summary whose subtotal is the API's. */\nexport class NymbalCartPage extends NymbalElement {\n static override schema = SCHEMA\n static uses = [NymbalQuantitySelector]\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n buttonStyles,\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~='loading'],\n [part~='empty'] {\n padding: 4rem var(--nymbal-spacing-md, 1rem);\n text-align: center;\n }\n /* Until the cart is known the page keeps what follows it below the fold, so a cart with lines does not push\n the footer down as it arrives. */\n [part~='loading'] {\n min-height: 100vh;\n }\n [part~='empty-title'] {\n margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-lg, 1.125rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='empty-text'] {\n margin: 0 0 var(--nymbal-spacing-lg, 1.5rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text-muted, #71717a);\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 360px;\n align-items: start;\n }\n }\n [part~='header'] {\n margin-bottom: var(--nymbal-spacing-lg, 1.5rem);\n }\n [part~='title'] {\n margin: 0;\n font-size: 1.5rem;\n font-weight: var(--nymbal-font-weight-bold, 700);\n color: var(--nymbal-color-primary, #18181b);\n }\n [part~='count'] {\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-normal, 400);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='items'] {\n display: flex;\n flex-direction: column;\n }\n [part~='item'] {\n display: grid;\n grid-template-columns: 80px 1fr auto;\n gap: var(--nymbal-spacing-md, 1rem);\n padding: var(--nymbal-spacing-md, 1rem) 0;\n border-bottom: 1px solid var(--nymbal-color-border, #e4e4e7);\n align-items: start;\n }\n [part~='item']:first-child {\n border-top: 1px solid var(--nymbal-color-border, #e4e4e7);\n }\n @media (min-width: 641px) {\n [part~='item'] {\n grid-template-columns: 100px 1fr auto auto;\n gap: var(--nymbal-spacing-lg, 1.5rem);\n align-items: center;\n }\n }\n [part~='item-image'] {\n display: block;\n width: 100%;\n aspect-ratio: 1 / 1;\n object-fit: cover;\n border-radius: var(--nymbal-radius-md, 0.5rem);\n background-color: #f4f4f5;\n }\n [part~='item-info'] {\n display: flex;\n flex-direction: column;\n gap: var(--nymbal-spacing-xs, 0.25rem);\n min-width: 0;\n }\n [part~='item-name'] {\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n color: var(--nymbal-color-text, #18181b);\n text-decoration: none;\n }\n [part~='item-name']:hover {\n color: var(--nymbal-color-accent, #2563eb);\n }\n [part~='item-variant'],\n [part~='item-price'] {\n font-size: var(--nymbal-font-size-xs, 0.75rem);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='item-remove'] {\n align-self: start;\n padding: 0;\n font: inherit;\n font-size: var(--nymbal-font-size-xs, 0.75rem);\n color: var(--nymbal-color-text-muted, #71717a);\n background: none;\n border: none;\n text-decoration: underline;\n cursor: pointer;\n }\n [part~='item-remove']:hover {\n color: var(--nymbal-color-error, #dc2626);\n }\n [part~='item-total'] {\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n text-align: right;\n white-space: nowrap;\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 border-bottom: 1px solid var(--nymbal-color-border, #e4e4e7);\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-md, 1rem);\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 border-top: 1px solid var(--nymbal-color-border, #e4e4e7);\n }\n [part~='actions'] {\n display: flex;\n flex-direction: column;\n gap: var(--nymbal-spacing-sm, 0.5rem);\n margin-top: var(--nymbal-spacing-lg, 1.5rem);\n }\n `,\n ]\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 /** The first line's image is at the top of the page and is often its LCP, so only the lines below it load lazily. */\n private renderLine(item: CartItem, currency: string, index: number) {\n return html`\n <div part=\"item\" data-testid=\"cart-item-${item.variantId}\">\n ${item.imageUrl\n ? html`<img\n part=\"item-image\"\n src=${item.imageUrl}\n alt=${item.productName}\n width=\"100\"\n height=\"100\"\n loading=${index === 0 ? 'eager' : 'lazy'}\n />`\n : html`<div part=\"item-image\" aria-hidden=\"true\"></div>`}\n <div part=\"item-info\">\n <a part=\"item-name\" href=\"/product/${encodeURIComponent(item.productId)}\"\n >${item.productName}</a\n >\n ${item.variantName ? html`<span part=\"item-variant\">${item.variantName}</span>` : nothing}\n <span part=\"item-price\">${formatPrice(item.priceMinor, currency)} each</span>\n <button\n type=\"button\"\n part=\"item-remove\"\n data-testid=\"cart-remove-${item.variantId}\"\n aria-label=\"Remove ${item.productName} from cart\"\n @click=${() => void this.client.cart.removeItem(item.variantId)}\n >\n Remove\n </button>\n </div>\n <div part=\"item-qty\">\n <nymbal-quantity-selector\n value=${item.qty}\n min=\"1\"\n max=\"99\"\n @nymbal:quantity:changed=${(e: CustomEvent<{ value: number }>) =>\n void this.client.cart.updateQuantity(item.variantId, e.detail.value)}\n ></nymbal-quantity-selector>\n </div>\n <span part=\"item-total\">${formatPrice(item.priceMinor * item.qty, currency)}</span>\n </div>\n `\n }\n\n protected override render() {\n const state = shownCart(this, this.cart, this.readsStores ? this.client : null)\n const { items } = state\n const currency = state.currency || 'GBP'\n\n if (state.loading && items.length === 0) {\n return html`<div part=\"loading\" role=\"status\">Loading your cart...</div>`\n }\n if (items.length === 0) {\n return html`\n <div part=\"empty\">\n <slot name=\"empty\">\n <h1 part=\"empty-title\">Your cart is empty</h1>\n <p part=\"empty-text\">Looks like you haven't added anything yet.</p>\n </slot>\n <a part=\"continue\" class=\"btn btn-primary\" href=\"/products\" data-testid=\"continue-shopping\"\n >Continue Shopping</a\n >\n </div>\n `\n }\n\n const subtotal = formatPrice(state.subtotalMinor, currency)\n return html`\n <div part=\"layout\">\n <div>\n <div part=\"header\">\n <h1 part=\"title\">\n <slot name=\"title\">Your Cart</slot>\n <span part=\"count\">(${items.length} ${items.length === 1 ? 'item' : 'items'})</span>\n </h1>\n </div>\n <div part=\"items\" data-testid=\"cart-items\">\n ${items.map((item, index) => this.renderLine(item, currency, index))}\n </div>\n </div>\n <div part=\"summary\" data-testid=\"cart-summary\">\n <h2 part=\"summary-title\">Order Summary</h2>\n <dl>\n <div part=\"summary-row subtotal\">\n <dt>Subtotal</dt>\n <dd>${subtotal}</dd>\n </div>\n <div part=\"summary-row\">\n <dt>Shipping</dt>\n <dd>Calculated at checkout</dd>\n </div>\n </dl>\n <div part=\"total\">\n <span>Estimated Total</span>\n <span>${subtotal}</span>\n </div>\n <div part=\"actions\">\n <a part=\"checkout\" class=\"btn btn-primary\" href=\"/checkout\" data-testid=\"checkout-btn\"\n ><slot name=\"checkout-label\">Proceed to Checkout</slot></a\n >\n <a part=\"continue\" class=\"btn btn-secondary\" href=\"/products\">Continue Shopping</a>\n </div>\n </div>\n </div>\n `\n }\n}\n"],"mappings":"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","names":[]}