@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,148 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalFormElement
4
+ } from "./chunk-PN6QKYE4.js";
5
+ import {
6
+ buttonStyles,
7
+ fieldStyles
8
+ } from "./chunk-GHXTJ2CF.js";
9
+ import {
10
+ baseStyles,
11
+ propertiesOf
12
+ } from "./chunk-EYDMJNNK.js";
13
+
14
+ // src/components/review-form.ts
15
+ import { css, html, nothing } from "lit";
16
+ var SCHEMA = {
17
+ tag: "nymbal-review-form",
18
+ reactName: "ReviewForm",
19
+ attributes: [{ name: "product-id", prop: "productId", type: "string" }],
20
+ events: [
21
+ {
22
+ name: "nymbal:review:submitted",
23
+ prop: "onSubmitted",
24
+ detail: "{ reviewId: string }"
25
+ }
26
+ ],
27
+ parts: ["form", "rating", "title", "body", "submit", "error"],
28
+ slots: [],
29
+ children: false
30
+ };
31
+ var RATINGS = [
32
+ ["5", "5 - Excellent"],
33
+ ["4", "4 - Very Good"],
34
+ ["3", "3 - Good"],
35
+ ["2", "2 - Fair"],
36
+ ["1", "1 - Poor"]
37
+ ];
38
+ var NymbalReviewForm = class extends NymbalFormElement {
39
+ static schema = SCHEMA;
40
+ static properties = propertiesOf(SCHEMA);
41
+ static styles = [
42
+ baseStyles,
43
+ buttonStyles,
44
+ fieldStyles,
45
+ css`
46
+ :host {
47
+ max-width: 32rem;
48
+ }
49
+ h3 {
50
+ margin: 0 0 var(--nymbal-spacing-md, 1rem);
51
+ line-height: 1.2;
52
+ font-size: var(--nymbal-font-size-lg, 1.125rem);
53
+ font-weight: var(--nymbal-font-weight-bold, 700);
54
+ }
55
+ label {
56
+ display: block;
57
+ margin-bottom: var(--nymbal-spacing-xs, 0.25rem);
58
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
59
+ font-weight: var(--nymbal-font-weight-medium, 500);
60
+ }
61
+ [part~='rating'],
62
+ [part~='title'],
63
+ [part~='body'] {
64
+ display: block;
65
+ width: 100%;
66
+ margin-bottom: var(--nymbal-spacing-md, 1rem);
67
+ padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);
68
+ font: inherit;
69
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
70
+ color: var(--nymbal-color-text, #18181b);
71
+ background-color: var(--nymbal-color-surface, #ffffff);
72
+ border: 1px solid var(--nymbal-color-border, #e4e4e7);
73
+ border-radius: var(--nymbal-radius-md, 0.5rem);
74
+ }
75
+ [part~='body'] {
76
+ resize: vertical;
77
+ }
78
+ .notice {
79
+ margin-top: var(--nymbal-spacing-md, 1rem);
80
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
81
+ color: var(--nymbal-color-success, #16a34a);
82
+ }
83
+ `
84
+ ];
85
+ async send() {
86
+ const answer = await this.client.adapter.reviews.submit({
87
+ productId: this.productId ?? "",
88
+ rating: Number(this.value("rating")),
89
+ title: this.value("title"),
90
+ body: this.value("body")
91
+ });
92
+ this.renderRoot.querySelector("form")?.reset();
93
+ this.notice = "Thank you for your review!";
94
+ const reviewId = answer?.review?.id;
95
+ if (reviewId !== void 0) this.emit("nymbal:review:submitted", { reviewId });
96
+ }
97
+ failureText() {
98
+ return "Something went wrong. Please try again.";
99
+ }
100
+ render() {
101
+ return html`
102
+ ${this.renderError()}
103
+ <form part="form" data-testid="review-form" @submit=${this.onSubmit}>
104
+ <h3>Write a Review</h3>
105
+ <label for="review-rating">Rating</label>
106
+ <select part="rating" id="review-rating" name="rating" required data-testid="review-rating">
107
+ <option value="">Select a rating</option>
108
+ ${RATINGS.map(([value, label]) => html`<option value=${value}>${label}</option>`)}
109
+ </select>
110
+ <label for="review-title">Title</label>
111
+ <input
112
+ part="title"
113
+ id="review-title"
114
+ name="title"
115
+ type="text"
116
+ placeholder="Summarise your experience"
117
+ required
118
+ data-testid="review-title"
119
+ />
120
+ <label for="review-body">Review</label>
121
+ <textarea
122
+ part="body"
123
+ id="review-body"
124
+ name="body"
125
+ rows="4"
126
+ placeholder="Tell us what you think..."
127
+ required
128
+ data-testid="review-body"
129
+ ></textarea>
130
+ <button
131
+ type="submit"
132
+ part="submit"
133
+ class="btn btn-primary"
134
+ data-testid="review-submit"
135
+ ?disabled=${this.pending}
136
+ >
137
+ ${this.pending ? "Submitting..." : "Submit Review"}
138
+ </button>
139
+ ${this.notice === null ? nothing : html`<p class="notice" role="status">${this.notice}</p>`}
140
+ </form>
141
+ `;
142
+ }
143
+ };
144
+
145
+ export {
146
+ NymbalReviewForm
147
+ };
148
+ //# sourceMappingURL=chunk-NE6UHTOT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/review-form.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport { baseStyles, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { NymbalFormElement } from '../form-element.js'\nimport { buttonStyles, fieldStyles } from '../view-styles.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-review-form',\n reactName: 'ReviewForm',\n attributes: [{ name: 'product-id', prop: 'productId', type: 'string' }],\n events: [\n {\n name: 'nymbal:review:submitted',\n prop: 'onSubmitted',\n detail: '{ reviewId: string }',\n },\n ],\n parts: ['form', 'rating', 'title', 'body', 'submit', 'error'],\n slots: [],\n children: false,\n}\n\nconst RATINGS: Array<[string, string]> = [\n ['5', '5 - Excellent'],\n ['4', '4 - Very Good'],\n ['3', '3 - Good'],\n ['2', '2 - Fair'],\n ['1', '1 - Poor'],\n]\n\n/** A review of one product: one submission per click, the form cleared once the store has it. */\nexport class NymbalReviewForm extends NymbalFormElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n buttonStyles,\n fieldStyles,\n css`\n :host {\n max-width: 32rem;\n }\n h3 {\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 label {\n display: block;\n margin-bottom: var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='rating'],\n [part~='title'],\n [part~='body'] {\n display: block;\n width: 100%;\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);\n font: inherit;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text, #18181b);\n background-color: var(--nymbal-color-surface, #ffffff);\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-md, 0.5rem);\n }\n [part~='body'] {\n resize: vertical;\n }\n .notice {\n margin-top: var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-success, #16a34a);\n }\n `,\n ]\n\n declare productId: string | undefined\n\n protected override async send(): Promise<void> {\n const answer: { review?: { id: string } | null } | null | undefined =\n await this.client.adapter.reviews.submit({\n productId: this.productId ?? '',\n rating: Number(this.value('rating')),\n title: this.value('title'),\n body: this.value('body'),\n })\n this.renderRoot.querySelector('form')?.reset()\n this.notice = 'Thank you for your review!'\n // A 2xx answer is the store's acceptance; without a review in it there is no id to announce.\n const reviewId = answer?.review?.id\n if (reviewId !== undefined) this.emit('nymbal:review:submitted', { reviewId })\n }\n\n protected override failureText(): string {\n return 'Something went wrong. Please try again.'\n }\n\n protected override render() {\n return html`\n ${this.renderError()}\n <form part=\"form\" data-testid=\"review-form\" @submit=${this.onSubmit}>\n <h3>Write a Review</h3>\n <label for=\"review-rating\">Rating</label>\n <select part=\"rating\" id=\"review-rating\" name=\"rating\" required data-testid=\"review-rating\">\n <option value=\"\">Select a rating</option>\n ${RATINGS.map(([value, label]) => html`<option value=${value}>${label}</option>`)}\n </select>\n <label for=\"review-title\">Title</label>\n <input\n part=\"title\"\n id=\"review-title\"\n name=\"title\"\n type=\"text\"\n placeholder=\"Summarise your experience\"\n required\n data-testid=\"review-title\"\n />\n <label for=\"review-body\">Review</label>\n <textarea\n part=\"body\"\n id=\"review-body\"\n name=\"body\"\n rows=\"4\"\n placeholder=\"Tell us what you think...\"\n required\n data-testid=\"review-body\"\n ></textarea>\n <button\n type=\"submit\"\n part=\"submit\"\n class=\"btn btn-primary\"\n data-testid=\"review-submit\"\n ?disabled=${this.pending}\n >\n ${this.pending ? 'Submitting...' : 'Submit Review'}\n </button>\n ${this.notice === null ? nothing : html`<p class=\"notice\" role=\"status\">${this.notice}</p>`}\n </form>\n `\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,SAAS,KAAK,MAAM,eAAe;AAKnC,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA,EACX,YAAY,CAAC,EAAE,MAAM,cAAc,MAAM,aAAa,MAAM,SAAS,CAAC;AAAA,EACtE,QAAQ;AAAA,IACN;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,OAAO,CAAC,QAAQ,UAAU,SAAS,QAAQ,UAAU,OAAO;AAAA,EAC5D,OAAO,CAAC;AAAA,EACR,UAAU;AACZ;AAEA,IAAM,UAAmC;AAAA,EACvC,CAAC,KAAK,eAAe;AAAA,EACrB,CAAC,KAAK,eAAe;AAAA,EACrB,CAAC,KAAK,UAAU;AAAA,EAChB,CAAC,KAAK,UAAU;AAAA,EAChB,CAAC,KAAK,UAAU;AAClB;AAGO,IAAM,mBAAN,cAA+B,kBAAkB;AAAA,EACtD,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAChD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA,IACA;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,EAuCF;AAAA,EAIA,MAAyB,OAAsB;AAC7C,UAAM,SACJ,MAAM,KAAK,OAAO,QAAQ,QAAQ,OAAO;AAAA,MACvC,WAAW,KAAK,aAAa;AAAA,MAC7B,QAAQ,OAAO,KAAK,MAAM,QAAQ,CAAC;AAAA,MACnC,OAAO,KAAK,MAAM,OAAO;AAAA,MACzB,MAAM,KAAK,MAAM,MAAM;AAAA,IACzB,CAAC;AACH,SAAK,WAAW,cAAc,MAAM,GAAG,MAAM;AAC7C,SAAK,SAAS;AAEd,UAAM,WAAW,QAAQ,QAAQ;AACjC,QAAI,aAAa,OAAW,MAAK,KAAK,2BAA2B,EAAE,SAAS,CAAC;AAAA,EAC/E;AAAA,EAEmB,cAAsB;AACvC,WAAO;AAAA,EACT;AAAA,EAEmB,SAAS;AAC1B,WAAO;AAAA,QACH,KAAK,YAAY,CAAC;AAAA,4DACkC,KAAK,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,YAK7D,QAAQ,IAAI,CAAC,CAAC,OAAO,KAAK,MAAM,qBAAqB,KAAK,IAAI,KAAK,WAAW,CAAC;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,sBA2BrE,KAAK,OAAO;AAAA;AAAA,YAEtB,KAAK,UAAU,kBAAkB,eAAe;AAAA;AAAA,UAElD,KAAK,WAAW,OAAO,UAAU,uCAAuC,KAAK,MAAM,MAAM;AAAA;AAAA;AAAA,EAGjG;AACF;","names":[]}
@@ -0,0 +1,138 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalFormElement,
4
+ renderField
5
+ } from "./chunk-PN6QKYE4.js";
6
+ import {
7
+ buttonStyles,
8
+ fieldStyles
9
+ } from "./chunk-GHXTJ2CF.js";
10
+ import {
11
+ baseStyles,
12
+ propertiesOf
13
+ } from "./chunk-EYDMJNNK.js";
14
+
15
+ // src/components/register-form.ts
16
+ import { css, html } from "lit";
17
+ var SCHEMA = {
18
+ tag: "nymbal-register-form",
19
+ reactName: "RegisterForm",
20
+ attributes: [],
21
+ events: [
22
+ {
23
+ name: "nymbal:auth:signed-in",
24
+ prop: "onSignedIn",
25
+ detail: "{ customer: unknown }"
26
+ }
27
+ ],
28
+ parts: ["form", "row", "field", "label", "input", "error", "submit"],
29
+ slots: ["submit-label"],
30
+ children: false
31
+ };
32
+ var NymbalRegisterForm = class extends NymbalFormElement {
33
+ static schema = SCHEMA;
34
+ static properties = propertiesOf(SCHEMA);
35
+ static styles = [
36
+ baseStyles,
37
+ buttonStyles,
38
+ fieldStyles,
39
+ css`
40
+ [part~='row'] {
41
+ display: grid;
42
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
43
+ gap: 0 var(--nymbal-spacing-md, 1rem);
44
+ }
45
+ `
46
+ ];
47
+ // The page leaves on success; a second submit while it does would try to register the email twice.
48
+ lockOnSuccess = true;
49
+ async send() {
50
+ if (this.value("password") !== this.value("confirmPassword")) {
51
+ throw new Error("Passwords do not match.");
52
+ }
53
+ await this.client.auth.register({
54
+ email: this.value("email"),
55
+ password: this.value("password"),
56
+ firstName: this.value("firstName"),
57
+ lastName: this.value("lastName"),
58
+ phone: this.value("phone")
59
+ });
60
+ this.emit("nymbal:auth:signed-in", { customer: this.client.auth.getState().customer });
61
+ }
62
+ failureText(err) {
63
+ return err instanceof Error && err.message !== "" ? err.message : "Registration failed. Please try again.";
64
+ }
65
+ render() {
66
+ return html`
67
+ ${this.renderError()}
68
+ <form part="form" data-testid="register-form" @submit=${this.onSubmit}>
69
+ <div part="row">
70
+ ${renderField({
71
+ id: "register-firstName",
72
+ name: "firstName",
73
+ label: "First Name",
74
+ autocomplete: "given-name",
75
+ placeholder: "First name"
76
+ })}
77
+ ${renderField({
78
+ id: "register-lastName",
79
+ name: "lastName",
80
+ label: "Last Name",
81
+ autocomplete: "family-name",
82
+ placeholder: "Last name"
83
+ })}
84
+ </div>
85
+ ${renderField({
86
+ id: "register-email",
87
+ name: "email",
88
+ label: "Email",
89
+ type: "email",
90
+ autocomplete: "email",
91
+ required: true,
92
+ placeholder: "you@example.com"
93
+ })}
94
+ ${renderField({
95
+ id: "register-password",
96
+ name: "password",
97
+ label: "Password",
98
+ type: "password",
99
+ autocomplete: "new-password",
100
+ required: true,
101
+ minlength: 8,
102
+ placeholder: "Choose a password"
103
+ })}
104
+ ${renderField({
105
+ id: "register-confirmPassword",
106
+ name: "confirmPassword",
107
+ label: "Confirm Password",
108
+ type: "password",
109
+ autocomplete: "new-password",
110
+ required: true,
111
+ placeholder: "Confirm your password"
112
+ })}
113
+ ${renderField({
114
+ id: "register-phone",
115
+ name: "phone",
116
+ label: "Phone (optional)",
117
+ type: "tel",
118
+ autocomplete: "tel",
119
+ placeholder: "+44 7700 900000"
120
+ })}
121
+ <button
122
+ type="submit"
123
+ part="submit"
124
+ class="btn btn-primary"
125
+ data-testid="register-submit"
126
+ ?disabled=${this.pending}
127
+ >
128
+ ${this.pending ? "Creating account..." : html`<slot name="submit-label">Create Account</slot>`}
129
+ </button>
130
+ </form>
131
+ `;
132
+ }
133
+ };
134
+
135
+ export {
136
+ NymbalRegisterForm
137
+ };
138
+ //# sourceMappingURL=chunk-NW47OJVN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/register-form.ts"],"sourcesContent":["import { css, html } from 'lit'\nimport { baseStyles, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { NymbalFormElement, renderField } from '../form-element.js'\nimport { buttonStyles, fieldStyles } from '../view-styles.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-register-form',\n reactName: 'RegisterForm',\n attributes: [],\n events: [\n {\n name: 'nymbal:auth:signed-in',\n prop: 'onSignedIn',\n detail: '{ customer: unknown }',\n },\n ],\n parts: ['form', 'row', 'field', 'label', 'input', 'error', 'submit'],\n slots: ['submit-label'],\n children: false,\n}\n\n/** Creates the shopper's account and signs them in; the page decides where they go next. */\nexport class NymbalRegisterForm extends NymbalFormElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n buttonStyles,\n fieldStyles,\n css`\n [part~='row'] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n gap: 0 var(--nymbal-spacing-md, 1rem);\n }\n `,\n ]\n\n // The page leaves on success; a second submit while it does would try to register the email twice.\n protected override readonly lockOnSuccess = true\n\n protected override async send(): Promise<void> {\n if (this.value('password') !== this.value('confirmPassword')) {\n throw new Error('Passwords do not match.')\n }\n await this.client.auth.register({\n email: this.value('email'),\n password: this.value('password'),\n firstName: this.value('firstName'),\n lastName: this.value('lastName'),\n phone: this.value('phone'),\n })\n this.emit('nymbal:auth:signed-in', { customer: this.client.auth.getState().customer })\n }\n\n protected override failureText(err: unknown): string {\n return err instanceof Error && err.message !== ''\n ? err.message\n : 'Registration failed. Please try again.'\n }\n\n protected override render() {\n return html`\n ${this.renderError()}\n <form part=\"form\" data-testid=\"register-form\" @submit=${this.onSubmit}>\n <div part=\"row\">\n ${renderField({\n id: 'register-firstName',\n name: 'firstName',\n label: 'First Name',\n autocomplete: 'given-name',\n placeholder: 'First name',\n })}\n ${renderField({\n id: 'register-lastName',\n name: 'lastName',\n label: 'Last Name',\n autocomplete: 'family-name',\n placeholder: 'Last name',\n })}\n </div>\n ${renderField({\n id: 'register-email',\n name: 'email',\n label: 'Email',\n type: 'email',\n autocomplete: 'email',\n required: true,\n placeholder: 'you@example.com',\n })}\n ${renderField({\n id: 'register-password',\n name: 'password',\n label: 'Password',\n type: 'password',\n autocomplete: 'new-password',\n required: true,\n minlength: 8,\n placeholder: 'Choose a password',\n })}\n ${renderField({\n id: 'register-confirmPassword',\n name: 'confirmPassword',\n label: 'Confirm Password',\n type: 'password',\n autocomplete: 'new-password',\n required: true,\n placeholder: 'Confirm your password',\n })}\n ${renderField({\n id: 'register-phone',\n name: 'phone',\n label: 'Phone (optional)',\n type: 'tel',\n autocomplete: 'tel',\n placeholder: '+44 7700 900000',\n })}\n <button\n type=\"submit\"\n part=\"submit\"\n class=\"btn btn-primary\"\n data-testid=\"register-submit\"\n ?disabled=${this.pending}\n >\n ${this.pending ? 'Creating account...' : html`<slot name=\"submit-label\">Create Account</slot>`}\n </button>\n </form>\n `\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAAA,SAAS,KAAK,YAAY;AAK1B,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA,EACX,YAAY,CAAC;AAAA,EACb,QAAQ;AAAA,IACN;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,OAAO,CAAC,QAAQ,OAAO,SAAS,SAAS,SAAS,SAAS,QAAQ;AAAA,EACnE,OAAO,CAAC,cAAc;AAAA,EACtB,UAAU;AACZ;AAGO,IAAM,qBAAN,cAAiC,kBAAkB;AAAA,EACxD,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAChD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOF;AAAA;AAAA,EAG4B,gBAAgB;AAAA,EAE5C,MAAyB,OAAsB;AAC7C,QAAI,KAAK,MAAM,UAAU,MAAM,KAAK,MAAM,iBAAiB,GAAG;AAC5D,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AACA,UAAM,KAAK,OAAO,KAAK,SAAS;AAAA,MAC9B,OAAO,KAAK,MAAM,OAAO;AAAA,MACzB,UAAU,KAAK,MAAM,UAAU;AAAA,MAC/B,WAAW,KAAK,MAAM,WAAW;AAAA,MACjC,UAAU,KAAK,MAAM,UAAU;AAAA,MAC/B,OAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,CAAC;AACD,SAAK,KAAK,yBAAyB,EAAE,UAAU,KAAK,OAAO,KAAK,SAAS,EAAE,SAAS,CAAC;AAAA,EACvF;AAAA,EAEmB,YAAY,KAAsB;AACnD,WAAO,eAAe,SAAS,IAAI,YAAY,KAC3C,IAAI,UACJ;AAAA,EACN;AAAA,EAEmB,SAAS;AAC1B,WAAO;AAAA,QACH,KAAK,YAAY,CAAC;AAAA,8DACoC,KAAK,QAAQ;AAAA;AAAA,YAE/D,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,YACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,IACf,CAAC,CAAC;AAAA;AAAA,UAEF,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,UAAU;AAAA,MACV,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,UACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,UACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,UAAU;AAAA,MACV,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,UACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,aAAa;AAAA,IACf,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAMY,KAAK,OAAO;AAAA;AAAA,YAEtB,KAAK,UAAU,wBAAwB,qDAAqD;AAAA;AAAA;AAAA;AAAA,EAItG;AACF;","names":[]}
@@ -0,0 +1,77 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalVariantElement
4
+ } from "./chunk-UYSZOEOA.js";
5
+ import {
6
+ baseStyles,
7
+ propertiesOf
8
+ } from "./chunk-EYDMJNNK.js";
9
+
10
+ // src/components/stock-badge.ts
11
+ import { css, html, nothing } from "lit";
12
+ import { STOCK_LABELS, stockLevel } from "@nymbalhq/types";
13
+ var SCHEMA = {
14
+ tag: "nymbal-stock-badge",
15
+ reactName: "StockBadge",
16
+ // The product's first variant's stock as a server read it, and the product whose store updates it may follow.
17
+ attributes: [
18
+ { name: "product-id", prop: "productId", type: "string" },
19
+ { name: "stock", prop: "stock", type: "number" }
20
+ ],
21
+ events: [],
22
+ parts: ["badge", "label"],
23
+ slots: [],
24
+ children: false
25
+ };
26
+ var NymbalStockBadge = class extends NymbalVariantElement {
27
+ static schema = SCHEMA;
28
+ static properties = propertiesOf(SCHEMA);
29
+ static styles = [
30
+ baseStyles,
31
+ css`
32
+ :host {
33
+ display: block;
34
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
35
+ }
36
+ [part~='badge'] {
37
+ display: inline-flex;
38
+ align-items: center;
39
+ gap: var(--nymbal-spacing-xs, 0.25rem);
40
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
41
+ font-weight: var(--nymbal-font-weight-medium, 500);
42
+ }
43
+ [part~='badge'][data-level='in'] {
44
+ color: var(--nymbal-color-success, #16a34a);
45
+ }
46
+ [part~='badge'][data-level='low'] {
47
+ color: var(--nymbal-color-warning, #d97706);
48
+ }
49
+ [part~='badge'][data-level='out'] {
50
+ color: var(--nymbal-color-error, #dc2626);
51
+ }
52
+ .dot {
53
+ width: 8px;
54
+ height: 8px;
55
+ border-radius: var(--nymbal-radius-full, 9999px);
56
+ background-color: currentColor;
57
+ }
58
+ `
59
+ ];
60
+ render() {
61
+ const store = this.selected();
62
+ const stock = store === null ? this.stock : store.variant?.stock ?? 0;
63
+ if (stock === void 0 || Number.isNaN(stock)) return nothing;
64
+ const level = stockLevel(stock);
65
+ return html`
66
+ <span part="badge" data-testid="stock-badge" data-level=${level}>
67
+ <span class="dot" aria-hidden="true"></span>
68
+ <span part="label">${STOCK_LABELS[level]}</span>
69
+ </span>
70
+ `;
71
+ }
72
+ };
73
+
74
+ export {
75
+ NymbalStockBadge
76
+ };
77
+ //# sourceMappingURL=chunk-O2YKCDR2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/stock-badge.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport { STOCK_LABELS, stockLevel } from '@nymbalhq/types'\nimport { baseStyles, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { NymbalVariantElement } from '../selected-variant.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-stock-badge',\n reactName: 'StockBadge',\n // The product's first variant's stock 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: 'stock', prop: 'stock', type: 'number' },\n ],\n events: [],\n parts: ['badge', 'label'],\n slots: [],\n children: false,\n}\n\n/** A product page's stock level (`@nymbalhq/types`' rule): the selected variant's once the store holds this product. */\nexport class NymbalStockBadge 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~='badge'] {\n display: inline-flex;\n align-items: center;\n gap: var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='badge'][data-level='in'] {\n color: var(--nymbal-color-success, #16a34a);\n }\n [part~='badge'][data-level='low'] {\n color: var(--nymbal-color-warning, #d97706);\n }\n [part~='badge'][data-level='out'] {\n color: var(--nymbal-color-error, #dc2626);\n }\n .dot {\n width: 8px;\n height: 8px;\n border-radius: var(--nymbal-radius-full, 9999px);\n background-color: currentColor;\n }\n `,\n ]\n\n declare stock: number | undefined\n\n protected override render() {\n const store = this.selected()\n // A product with no variants has nothing to sell, so it reads as out of stock.\n const stock = store === null ? this.stock : (store.variant?.stock ?? 0)\n if (stock === undefined || Number.isNaN(stock)) return nothing\n const level = stockLevel(stock)\n return html`\n <span part=\"badge\" data-testid=\"stock-badge\" data-level=${level}>\n <span class=\"dot\" aria-hidden=\"true\"></span>\n <span part=\"label\">${STOCK_LABELS[level]}</span>\n </span>\n `\n }\n}\n"],"mappings":";;;;;;;;;;AAAA,SAAS,KAAK,MAAM,eAAe;AACnC,SAAS,cAAc,kBAAkB;AAIzC,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,SAAS,MAAM,SAAS,MAAM,SAAS;AAAA,EACjD;AAAA,EACA,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,SAAS,OAAO;AAAA,EACxB,OAAO,CAAC;AAAA,EACR,UAAU;AACZ;AAGO,IAAM,mBAAN,cAA+B,qBAAqB;AAAA,EACzD,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,EA4BF;AAAA,EAImB,SAAS;AAC1B,UAAM,QAAQ,KAAK,SAAS;AAE5B,UAAM,QAAQ,UAAU,OAAO,KAAK,QAAS,MAAM,SAAS,SAAS;AACrE,QAAI,UAAU,UAAa,OAAO,MAAM,KAAK,EAAG,QAAO;AACvD,UAAM,QAAQ,WAAW,KAAK;AAC9B,WAAO;AAAA,gEACqD,KAAK;AAAA;AAAA,6BAExC,aAAa,KAAK,CAAC;AAAA;AAAA;AAAA,EAG9C;AACF;","names":[]}
@@ -0,0 +1,71 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalElement
4
+ } from "./chunk-EYDMJNNK.js";
5
+
6
+ // src/form-element.ts
7
+ import { html, nothing } from "lit";
8
+ function renderField(f) {
9
+ return html`
10
+ <div part="field">
11
+ <label part="label" for=${f.id}>${f.label}</label>
12
+ <input
13
+ part="input"
14
+ id=${f.id}
15
+ name=${f.name}
16
+ type=${f.type ?? "text"}
17
+ autocomplete=${f.autocomplete}
18
+ placeholder=${f.placeholder ?? nothing}
19
+ minlength=${f.minlength ?? nothing}
20
+ ?required=${f.required ?? false}
21
+ ?disabled=${f.disabled ?? false}
22
+ .value=${f.value ?? ""}
23
+ data-testid=${f.id}
24
+ />
25
+ </div>
26
+ `;
27
+ }
28
+ var NymbalFormElement = class extends NymbalElement {
29
+ pending = false;
30
+ error = null;
31
+ notice = null;
32
+ /** Keeps the submit disabled after a success, for a form whose page navigates away on it. */
33
+ lockOnSuccess = false;
34
+ /** The error text a refusal shows. */
35
+ failureText(err) {
36
+ return err instanceof Error && err.message !== "" ? err.message : "Something went wrong.";
37
+ }
38
+ onSubmit = (event) => {
39
+ event.preventDefault();
40
+ if (this.pending) return;
41
+ this.pending = true;
42
+ this.error = null;
43
+ this.notice = null;
44
+ const sent = this.send();
45
+ this.requestUpdate();
46
+ sent.then(
47
+ () => this.settle(null),
48
+ (err) => this.settle(this.failureText(err))
49
+ );
50
+ };
51
+ settle(error) {
52
+ this.pending = error === null && this.lockOnSuccess;
53
+ this.error = error;
54
+ this.requestUpdate();
55
+ }
56
+ value(name) {
57
+ return this.renderRoot.querySelector(`[name="${name}"]`)?.value ?? "";
58
+ }
59
+ emit(name, detail) {
60
+ this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail }));
61
+ }
62
+ renderError() {
63
+ return this.error === null ? nothing : html`<div part="error" role="alert">${this.error}</div>`;
64
+ }
65
+ };
66
+
67
+ export {
68
+ renderField,
69
+ NymbalFormElement
70
+ };
71
+ //# sourceMappingURL=chunk-PN6QKYE4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/form-element.ts"],"sourcesContent":["import { html, nothing, type TemplateResult } from 'lit'\nimport { NymbalElement } from './base-element.js'\n\nexport interface FieldSpec {\n /** The input's id and test id, unique within the element's shadow root. */\n id: string\n name: string\n label: string\n type?: 'text' | 'email' | 'password' | 'tel'\n autocomplete: string\n required?: boolean\n disabled?: boolean\n minlength?: number\n placeholder?: string\n /** Set on the input when it changes, so typing is kept across renders. */\n value?: string\n}\n\n/** A labelled input; the label's `for` and the input share the element's shadow root. */\nexport function renderField(f: FieldSpec): TemplateResult {\n return html`\n <div part=\"field\">\n <label part=\"label\" for=${f.id}>${f.label}</label>\n <input\n part=\"input\"\n id=${f.id}\n name=${f.name}\n type=${f.type ?? 'text'}\n autocomplete=${f.autocomplete}\n placeholder=${f.placeholder ?? nothing}\n minlength=${f.minlength ?? nothing}\n ?required=${f.required ?? false}\n ?disabled=${f.disabled ?? false}\n .value=${f.value ?? ''}\n data-testid=${f.id}\n />\n </div>\n `\n}\n\n/**\n * A view whose form makes one SDK call per submission. `requestSubmit()` and Enter still fire `submit` while the\n * button is disabled, so the pending flag, not the button, is the guard.\n */\nexport abstract class NymbalFormElement extends NymbalElement {\n protected pending = false\n protected error: string | null = null\n protected notice: string | null = null\n /** Keeps the submit disabled after a success, for a form whose page navigates away on it. */\n protected readonly lockOnSuccess: boolean = false\n\n /** Reads the fields and makes the call; it must call the SDK before its first `await`. */\n protected abstract send(): Promise<void>\n\n /** The error text a refusal shows. */\n protected failureText(err: unknown): string {\n return err instanceof Error && err.message !== '' ? err.message : 'Something went wrong.'\n }\n\n protected readonly onSubmit = (event: Event): void => {\n event.preventDefault()\n if (this.pending) return\n this.pending = true\n this.error = null\n this.notice = null\n const sent = this.send()\n this.requestUpdate()\n sent.then(\n () => this.settle(null),\n (err: unknown) => this.settle(this.failureText(err)),\n )\n }\n\n private settle(error: string | null): void {\n this.pending = error === null && this.lockOnSuccess\n this.error = error\n this.requestUpdate()\n }\n\n protected value(name: string): string {\n return this.renderRoot.querySelector<HTMLInputElement>(`[name=\"${name}\"]`)?.value ?? ''\n }\n\n protected emit(name: string, detail: unknown): void {\n this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail }))\n }\n\n protected renderError(): TemplateResult | typeof nothing {\n return this.error === null\n ? nothing\n : html`<div part=\"error\" role=\"alert\">${this.error}</div>`\n }\n}\n"],"mappings":";;;;;;AAAA,SAAS,MAAM,eAAoC;AAmB5C,SAAS,YAAY,GAA8B;AACxD,SAAO;AAAA;AAAA,gCAEuB,EAAE,EAAE,IAAI,EAAE,KAAK;AAAA;AAAA;AAAA,aAGlC,EAAE,EAAE;AAAA,eACF,EAAE,IAAI;AAAA,eACN,EAAE,QAAQ,MAAM;AAAA,uBACR,EAAE,YAAY;AAAA,sBACf,EAAE,eAAe,OAAO;AAAA,oBAC1B,EAAE,aAAa,OAAO;AAAA,oBACtB,EAAE,YAAY,KAAK;AAAA,oBACnB,EAAE,YAAY,KAAK;AAAA,iBACtB,EAAE,SAAS,EAAE;AAAA,sBACR,EAAE,EAAE;AAAA;AAAA;AAAA;AAI1B;AAMO,IAAe,oBAAf,cAAyC,cAAc;AAAA,EAClD,UAAU;AAAA,EACV,QAAuB;AAAA,EACvB,SAAwB;AAAA;AAAA,EAEf,gBAAyB;AAAA;AAAA,EAMlC,YAAY,KAAsB;AAC1C,WAAO,eAAe,SAAS,IAAI,YAAY,KAAK,IAAI,UAAU;AAAA,EACpE;AAAA,EAEmB,WAAW,CAAC,UAAuB;AACpD,UAAM,eAAe;AACrB,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,UAAM,OAAO,KAAK,KAAK;AACvB,SAAK,cAAc;AACnB,SAAK;AAAA,MACH,MAAM,KAAK,OAAO,IAAI;AAAA,MACtB,CAAC,QAAiB,KAAK,OAAO,KAAK,YAAY,GAAG,CAAC;AAAA,IACrD;AAAA,EACF;AAAA,EAEQ,OAAO,OAA4B;AACzC,SAAK,UAAU,UAAU,QAAQ,KAAK;AACtC,SAAK,QAAQ;AACb,SAAK,cAAc;AAAA,EACrB;AAAA,EAEU,MAAM,MAAsB;AACpC,WAAO,KAAK,WAAW,cAAgC,UAAU,IAAI,IAAI,GAAG,SAAS;AAAA,EACvF;AAAA,EAEU,KAAK,MAAc,QAAuB;AAClD,SAAK,cAAc,IAAI,YAAY,MAAM,EAAE,SAAS,MAAM,UAAU,MAAM,OAAO,CAAC,CAAC;AAAA,EACrF;AAAA,EAEU,cAA+C;AACvD,WAAO,KAAK,UAAU,OAClB,UACA,sCAAsC,KAAK,KAAK;AAAA,EACtD;AACF;","names":[]}
@@ -0,0 +1,188 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalProductCard
4
+ } from "./chunk-RZVFA3N5.js";
5
+ import {
6
+ buttonStyles
7
+ } from "./chunk-GHXTJ2CF.js";
8
+ import {
9
+ NymbalElement,
10
+ baseStyles,
11
+ parsedJson,
12
+ propertiesOf
13
+ } from "./chunk-EYDMJNNK.js";
14
+
15
+ // src/components/search-results.ts
16
+ import { css, html, nothing } from "lit";
17
+ var SCHEMA = {
18
+ tag: "nymbal-search-results",
19
+ reactName: "SearchResults",
20
+ // The search a server ran: the store's `results` (the card products) and the term they answer, shown until the
21
+ // search store settles.
22
+ attributes: [
23
+ { name: "results", prop: "results", type: "json" },
24
+ { name: "query", prop: "query", type: "string" }
25
+ ],
26
+ events: [],
27
+ parts: [
28
+ "query",
29
+ "loading",
30
+ "empty",
31
+ "empty-icon",
32
+ "empty-title",
33
+ "empty-text",
34
+ "header",
35
+ "count",
36
+ "grid",
37
+ "pagination",
38
+ "load-more"
39
+ ],
40
+ slots: ["no-term", "no-results"],
41
+ children: false
42
+ };
43
+ var NymbalSearchResults = class extends NymbalElement {
44
+ static schema = SCHEMA;
45
+ static uses = [NymbalProductCard];
46
+ static properties = propertiesOf(SCHEMA);
47
+ static styles = [
48
+ baseStyles,
49
+ buttonStyles,
50
+ css`
51
+ :host {
52
+ display: block;
53
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
54
+ color: var(--nymbal-color-text, #18181b);
55
+ }
56
+ [part~='query'] {
57
+ margin: 0 0 var(--nymbal-spacing-md, 1rem);
58
+ color: var(--nymbal-color-text-muted, #71717a);
59
+ }
60
+ [part~='loading'] {
61
+ padding: var(--nymbal-spacing-xl, 2rem) 0;
62
+ text-align: center;
63
+ color: var(--nymbal-color-text-muted, #71717a);
64
+ }
65
+ [part~='empty'] {
66
+ padding: 4rem var(--nymbal-spacing-md, 1rem);
67
+ text-align: center;
68
+ }
69
+ [part~='empty-icon'] {
70
+ font-size: 3rem;
71
+ margin-bottom: var(--nymbal-spacing-md, 1rem);
72
+ opacity: 0.3;
73
+ }
74
+ [part~='empty-title'] {
75
+ margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);
76
+ font-size: var(--nymbal-font-size-lg, 1.125rem);
77
+ font-weight: var(--nymbal-font-weight-medium, 500);
78
+ }
79
+ [part~='empty-text'] {
80
+ max-width: 28ch;
81
+ margin: 0 auto;
82
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
83
+ color: var(--nymbal-color-text-muted, #71717a);
84
+ }
85
+ [part~='header'] {
86
+ margin-bottom: var(--nymbal-spacing-md, 1rem);
87
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
88
+ color: var(--nymbal-color-text-muted, #71717a);
89
+ }
90
+ [part~='grid'] {
91
+ display: grid;
92
+ grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
93
+ gap: var(--nymbal-spacing-lg, 1.5rem);
94
+ }
95
+ [part~='pagination'] {
96
+ display: flex;
97
+ justify-content: center;
98
+ margin-top: var(--nymbal-spacing-xl, 2rem);
99
+ }
100
+ `
101
+ ];
102
+ stores() {
103
+ return [this.client.search];
104
+ }
105
+ storeSettled() {
106
+ this.results = void 0;
107
+ }
108
+ /**
109
+ * The results a server gave while they stand (`given`), else the search store's; with neither, no search. Given
110
+ * results are shown whether or not the server named their term.
111
+ */
112
+ shown() {
113
+ const given = parsedJson(this, "results", this.results);
114
+ if (given === void 0 && this.readsStores) {
115
+ return { ...this.client.search.getState(), given: false };
116
+ }
117
+ const results = given ?? [];
118
+ const query = this.query ?? "";
119
+ return { query, results, total: results.length, nextCursor: null, loading: false, given: true };
120
+ }
121
+ empty(title, text, slot) {
122
+ return html`
123
+ <div part="empty">
124
+ <div part="empty-icon" aria-hidden="true">&#128269;</div>
125
+ <slot name=${slot}>
126
+ <h2 part="empty-title">${title}</h2>
127
+ <p part="empty-text">${text}</p>
128
+ </slot>
129
+ </div>
130
+ `;
131
+ }
132
+ body(state) {
133
+ const { results, query } = state;
134
+ if (state.loading && results.length === 0) {
135
+ return html`<div part="loading" role="status">Searching...</div>`;
136
+ }
137
+ if (query === "" && !(state.given && results.length > 0)) {
138
+ return this.empty("Search our store", "Type a keyword to find products.", "no-term");
139
+ }
140
+ if (results.length === 0) {
141
+ return this.empty(
142
+ "No results found",
143
+ "Try a different search term or browse our categories.",
144
+ "no-results"
145
+ );
146
+ }
147
+ const total = state.total;
148
+ return html`
149
+ <div part="header">
150
+ <span part="count" data-testid="search-total"
151
+ >${total} result${total === 1 ? "" : "s"}</span
152
+ >
153
+ </div>
154
+ <div part="grid">
155
+ ${results.map(
156
+ (p) => html`<nymbal-product-card product=${JSON.stringify(p)}></nymbal-product-card>`
157
+ )}
158
+ </div>
159
+ ${state.nextCursor === null ? nothing : html`
160
+ <div part="pagination">
161
+ <button
162
+ type="button"
163
+ part="load-more"
164
+ class="btn btn-secondary"
165
+ data-testid="search-load-more"
166
+ ?disabled=${state.loading}
167
+ @click=${() => void this.client.search.loadMore()}
168
+ >
169
+ ${state.loading ? "Loading..." : "Load more"}
170
+ </button>
171
+ </div>
172
+ `}
173
+ `;
174
+ }
175
+ render() {
176
+ const state = this.shown();
177
+ const { query } = state;
178
+ return html`
179
+ ${query === "" ? nothing : html`<p part="query">Results for <strong>"${query}"</strong></p>`}
180
+ ${this.body(state)}
181
+ `;
182
+ }
183
+ };
184
+
185
+ export {
186
+ NymbalSearchResults
187
+ };
188
+ //# sourceMappingURL=chunk-RAOGZH3Z.js.map